using Microsoft.Extensions.Options; namespace ZB.MOM.WW.LocalDb.Replication; /// /// Validates at startup, accumulating every failure into one result. /// is only required for the initiator role, which the /// validator cannot distinguish from a passive node, so its format is checked only when present. /// public sealed class ReplicationOptionsValidator : IValidateOptions { // Also what keeps SyncBackgroundService's unchecked backoff doubling overflow-safe. private static readonly TimeSpan ReconnectBackoffCeiling = TimeSpan.FromDays(1); public ValidateOptionsResult Validate(string? name, ReplicationOptions options) { var failures = new List(); if (!string.IsNullOrEmpty(options.PeerAddress) && (!Uri.TryCreate(options.PeerAddress, UriKind.Absolute, out var uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))) failures.Add( $"LocalDb:Replication:PeerAddress must be an absolute http/https URI; got '{options.PeerAddress}'."); // ApiKey needs no validation: null/empty legitimately means "no key auth". if (options.FlushInterval <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:FlushInterval must be greater than zero; got {options.FlushInterval}."); if (options.MaxBatchSize <= 0) failures.Add($"LocalDb:Replication:MaxBatchSize must be greater than 0; got {options.MaxBatchSize}."); if (options.MaxOplogRows <= 0) failures.Add($"LocalDb:Replication:MaxOplogRows must be greater than 0; got {options.MaxOplogRows}."); if (options.MaxOplogAge <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:MaxOplogAge must be greater than zero; got {options.MaxOplogAge}."); if (options.TombstoneRetention <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:TombstoneRetention must be greater than zero; got {options.TombstoneRetention}."); if (options.ReconnectBackoffMax <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:ReconnectBackoffMax must be greater than zero; got {options.ReconnectBackoffMax}."); else if (options.ReconnectBackoffMax > ReconnectBackoffCeiling) failures.Add($"LocalDb:Replication:ReconnectBackoffMax must be at most {ReconnectBackoffCeiling}; got {options.ReconnectBackoffMax}."); if (options.MaintenanceInterval <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:MaintenanceInterval must be greater than zero; got {options.MaintenanceInterval}."); if (options.MaxHlcDriftAhead <= TimeSpan.Zero) failures.Add($"LocalDb:Replication:MaxHlcDriftAhead must be greater than zero; got {options.MaxHlcDriftAhead}."); return failures.Count > 0 ? ValidateOptionsResult.Fail(failures) : ValidateOptionsResult.Success; } }