using System.Text.Json; using System.Text.Json.Serialization; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers; /// /// Mutable, all-nullable form model for the driver resilience override. Binds the typed /// fields in DriverResilienceSection; null/blank = "use the driver's tier default", so a /// blank form serializes back to null (preserving DriverInstance.ResilienceConfig = null). /// Emits / reads the exact override JSON shape DriverResilienceOptionsParser consumes. /// public sealed class ResilienceFormModel { public static readonly string[] Capabilities = ["Read", "Write", "Discover", "Subscribe", "Probe", "AlarmSubscribe", "AlarmAcknowledge", "HistoryRead"]; /// Gets or sets the bulkhead max-concurrency override; null = use the tier default. public int? BulkheadMaxConcurrent { get; set; } /// Gets or sets the bulkhead max-queue-length override; null = use the tier default. public int? BulkheadMaxQueue { get; set; } /// Gets or sets the driver recycle-interval override, in seconds; null = use the tier default. public int? RecycleIntervalSeconds { get; set; } // capability name -> (timeout, retry, breaker), each nullable. /// Gets or sets the per-capability resilience overrides, keyed by capability name. public Dictionary Policies { get; set; } = Capabilities.ToDictionary(c => c, _ => new CapabilityRow(), StringComparer.OrdinalIgnoreCase); /// Per-capability timeout/retry/breaker override row; null fields fall back to the tier default. public sealed class CapabilityRow { /// Gets or sets the timeout override, in seconds; null = use the tier default. public int? TimeoutSeconds { get; set; } /// Gets or sets the retry-count override; null = use the tier default. public int? RetryCount { get; set; } /// Gets or sets the circuit-breaker failure-threshold override; null = use the tier default. public int? BreakerFailureThreshold { get; set; } /// Gets a value indicating whether all fields in this row are unset. public bool IsEmpty => TimeoutSeconds is null && RetryCount is null && BreakerFailureThreshold is null; } private static readonly JsonSerializerOptions ReadOpts = new() { PropertyNameCaseInsensitive = true }; private static readonly JsonSerializerOptions WriteOpts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; /// Parses the override JSON into a form model; malformed or blank JSON yields an empty (all-default) form. /// The raw resilience-override JSON, or null/blank if there is no override. /// A populated form model, or an all-default one when is blank or malformed. public static ResilienceFormModel FromJson(string? json) { var model = new ResilienceFormModel(); if (string.IsNullOrWhiteSpace(json)) return model; Shape? shape; try { shape = JsonSerializer.Deserialize(json, ReadOpts); } catch (JsonException) { return model; } // malformed -> empty form; raw view (next task) shows the text if (shape is null) return model; model.BulkheadMaxConcurrent = shape.BulkheadMaxConcurrent; model.BulkheadMaxQueue = shape.BulkheadMaxQueue; model.RecycleIntervalSeconds = shape.RecycleIntervalSeconds; if (shape.CapabilityPolicies is not null) foreach (var (cap, p) in shape.CapabilityPolicies) if (model.Policies.TryGetValue(cap, out var row)) { row.TimeoutSeconds = p.TimeoutSeconds; row.RetryCount = p.RetryCount; row.BreakerFailureThreshold = p.BreakerFailureThreshold; } return model; } /// Emit only the non-null overrides; returns null when nothing is overridden. /// The serialized override JSON, or null when no field in this form is overridden. public string? ToJson() { var caps = Policies .Where(kv => !kv.Value.IsEmpty) .ToDictionary(kv => kv.Key, kv => new PolicyShape { TimeoutSeconds = kv.Value.TimeoutSeconds, RetryCount = kv.Value.RetryCount, BreakerFailureThreshold = kv.Value.BreakerFailureThreshold, }); var hasAny = BulkheadMaxConcurrent is not null || BulkheadMaxQueue is not null || RecycleIntervalSeconds is not null || caps.Count > 0; if (!hasAny) return null; var shape = new Shape { BulkheadMaxConcurrent = BulkheadMaxConcurrent, BulkheadMaxQueue = BulkheadMaxQueue, RecycleIntervalSeconds = RecycleIntervalSeconds, CapabilityPolicies = caps.Count > 0 ? caps : null, }; return JsonSerializer.Serialize(shape, WriteOpts); } /// Wire shape of the resilience-override JSON, as consumed by DriverResilienceOptionsParser. private sealed class Shape { /// Gets or sets the bulkhead max-concurrency override. public int? BulkheadMaxConcurrent { get; set; } /// Gets or sets the bulkhead max-queue-length override. public int? BulkheadMaxQueue { get; set; } /// Gets or sets the driver recycle-interval override, in seconds. public int? RecycleIntervalSeconds { get; set; } /// Gets or sets the per-capability overrides, keyed by capability name. public Dictionary? CapabilityPolicies { get; set; } } /// Wire shape of a single capability's timeout/retry/breaker override. private sealed class PolicyShape { /// Gets or sets the timeout override, in seconds. public int? TimeoutSeconds { get; set; } /// Gets or sets the retry-count override. public int? RetryCount { get; set; } /// Gets or sets the circuit-breaker failure-threshold override. public int? BreakerFailureThreshold { get; set; } } }