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; }
}
}