using ZB.MOM.WW.Configuration; namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging; /// /// Validates at startup. Retention/purge values /// drive the daily purge PeriodicTimer (a zero/negative period trips /// ), the paging bounds gate query result /// sizes, and the write-queue capacity sizes a bounded channel; a /// MaxQueryPageSize below QueryPageSize would silently clamp the /// default page below itself. Registered with ValidateOnStart() so a bad /// ScadaBridge:SiteEventLog section fails fast at boot with a clear, /// key-naming message. /// public sealed class SiteEventLogOptionsValidator : OptionsValidatorBase { /// protected override void Validate(ValidationBuilder builder, SiteEventLogOptions options) { builder.RequireThat(options.RetentionDays > 0, $"ScadaBridge:SiteEventLog:RetentionDays must be greater than 0 " + $"(was {options.RetentionDays})."); builder.RequireThat(options.MaxStorageMb > 0, $"ScadaBridge:SiteEventLog:MaxStorageMb must be greater than 0 " + $"(was {options.MaxStorageMb})."); builder.RequireThat(!string.IsNullOrWhiteSpace(options.DatabasePath), "ScadaBridge:SiteEventLog:DatabasePath must be a non-empty path; " + "it is the SQLite file backing the site event log."); builder.RequireThat(options.QueryPageSize > 0, $"ScadaBridge:SiteEventLog:QueryPageSize must be greater than 0 " + $"(was {options.QueryPageSize})."); builder.RequireThat(options.WriteQueueCapacity > 0, $"ScadaBridge:SiteEventLog:WriteQueueCapacity must be greater than 0 " + $"(was {options.WriteQueueCapacity}); it bounds the background write channel."); builder.RequireThat(options.PurgeInterval > TimeSpan.Zero, $"ScadaBridge:SiteEventLog:PurgeInterval must be a positive duration " + $"(was {options.PurgeInterval}); it is the purge PeriodicTimer period."); // A NEGATIVE ceiling is a config error, not a synonym for "unlimited": 0 already // carries that meaning explicitly, and silently treating -1 the same way would hide // a typo that removes the only bound on a purge burst's oplog cost. builder.RequireThat(options.MaxPurgedRowsPerTick >= 0, $"ScadaBridge:SiteEventLog:MaxPurgedRowsPerTick must be 0 (unlimited) or greater " + $"(was {options.MaxPurgedRowsPerTick}); it bounds how many rows one purge tick deletes."); builder.RequireThat(options.PurgeCatchUpInterval > TimeSpan.Zero, $"ScadaBridge:SiteEventLog:PurgeCatchUpInterval must be a positive duration " + $"(was {options.PurgeCatchUpInterval}); it is the delay before the next tick when a " + "purge stopped at MaxPurgedRowsPerTick."); // A catch-up longer than the ordinary interval would make hitting the ceiling SLOW the // purge down, which inverts the whole point of the accelerated retry. Only compared when // both are positive (each owned above), so a bad value fires exactly one message. builder.RequireThat( !(options.PurgeInterval > TimeSpan.Zero && options.PurgeCatchUpInterval > TimeSpan.Zero && options.PurgeCatchUpInterval > options.PurgeInterval), $"ScadaBridge:SiteEventLog:PurgeCatchUpInterval ({options.PurgeCatchUpInterval}) must be " + $"less than or equal to PurgeInterval ({options.PurgeInterval}): it is an ACCELERATED " + "retry after a ceiling-limited tick, so a longer value would delay convergence instead."); // The hard page-size ceiling clamps a caller-supplied PageSize; a ceiling // below the default QueryPageSize would silently clamp the default below // itself. Only meaningful when both are positive (owned above), so stay // silent when either is non-positive rather than double-firing. builder.RequireThat( !(options.QueryPageSize > 0 && options.MaxQueryPageSize > 0 && options.MaxQueryPageSize < options.QueryPageSize), $"ScadaBridge:SiteEventLog:MaxQueryPageSize ({options.MaxQueryPageSize}) " + $"must be >= QueryPageSize ({options.QueryPageSize}): the ceiling clamps a caller's " + "requested page size, so it cannot be smaller than the default page size."); } }