80 lines
4.7 KiB
C#
80 lines
4.7 KiB
C#
using ZB.MOM.WW.Configuration;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Configuration;
|
|
|
|
/// <summary>
|
|
/// Validates <see cref="AuditLogOptions"/> on startup. The caps drive payload
|
|
/// truncation in the M2+ writers, so an unset/zero cap would let arbitrarily
|
|
/// large blobs into the central <c>AuditLog</c> table. <see cref="AuditLogOptions.ErrorCapBytes"/>
|
|
/// must be at least as large as <see cref="AuditLogOptions.DefaultCapBytes"/>
|
|
/// because the error cap is meant to capture <em>more</em> detail than the
|
|
/// happy-path summary, not less. <see cref="AuditLogOptions.RetentionDays"/> is
|
|
/// bounded to <c>[30, 3650]</c> to keep purge windows sane: too short would
|
|
/// drop in-flight investigations, too long would defeat the partition-switch
|
|
/// purge's purpose.
|
|
/// </summary>
|
|
public sealed class AuditLogOptionsValidator : OptionsValidatorBase<AuditLogOptions>
|
|
{
|
|
/// <summary>Inclusive lower bound for <see cref="AuditLogOptions.RetentionDays"/>.</summary>
|
|
public const int MinRetentionDays = 30;
|
|
|
|
/// <summary>Inclusive upper bound for <see cref="AuditLogOptions.RetentionDays"/>.</summary>
|
|
public const int MaxRetentionDays = 3650;
|
|
|
|
/// <summary>Inclusive lower bound for <see cref="AuditLogOptions.InboundMaxBytes"/> (8 KiB).</summary>
|
|
public const int MinInboundMaxBytes = 8_192;
|
|
|
|
/// <summary>Inclusive upper bound for <see cref="AuditLogOptions.InboundMaxBytes"/> (16 MiB).</summary>
|
|
public const int MaxInboundMaxBytes = 16_777_216;
|
|
|
|
/// <inheritdoc />
|
|
protected override void Validate(ValidationBuilder builder, AuditLogOptions options)
|
|
{
|
|
builder.RequireThat(options.DefaultCapBytes > 0,
|
|
$"AuditLog:{nameof(AuditLogOptions.DefaultCapBytes)} ({options.DefaultCapBytes}) " +
|
|
"must be > 0; it drives payload-summary truncation in audit writers.");
|
|
|
|
builder.RequireThat(options.ErrorCapBytes >= options.DefaultCapBytes,
|
|
$"AuditLog:{nameof(AuditLogOptions.ErrorCapBytes)} ({options.ErrorCapBytes}) " +
|
|
$"must be >= {nameof(AuditLogOptions.DefaultCapBytes)} ({options.DefaultCapBytes}); " +
|
|
"the error-row cap is intended to capture more detail than the happy-path summary.");
|
|
|
|
// Valid when RetentionDays is within [Min, Max] inclusive. The De Morgan'd
|
|
// guard !(below Min OR above Max) is equivalent to (>= Min AND <= Max).
|
|
builder.RequireThat(
|
|
!(options.RetentionDays < MinRetentionDays || options.RetentionDays > MaxRetentionDays),
|
|
$"AuditLog:{nameof(AuditLogOptions.RetentionDays)} ({options.RetentionDays}) " +
|
|
$"must be in [{MinRetentionDays}, {MaxRetentionDays}] days.");
|
|
|
|
// Valid when InboundMaxBytes is within [Min, Max] inclusive. The De Morgan'd
|
|
// guard !(below Min OR above Max) is equivalent to (>= Min AND <= Max).
|
|
builder.RequireThat(
|
|
!(options.InboundMaxBytes < MinInboundMaxBytes || options.InboundMaxBytes > MaxInboundMaxBytes),
|
|
$"AuditLog:{nameof(AuditLogOptions.InboundMaxBytes)} ({options.InboundMaxBytes}) " +
|
|
$"must be in [{MinInboundMaxBytes}, {MaxInboundMaxBytes}] bytes.");
|
|
|
|
// M5.5 (T3): per-channel retention overrides. Each entry must be keyed by a
|
|
// recognized AuditChannel name and carry a window in [MinRetentionDays,
|
|
// RetentionDays] — i.e. SHORTER than or equal to the global window. A longer
|
|
// per-channel window is meaningless under month-partition switch-out (governed
|
|
// by the global window), so it is rejected rather than silently ignored.
|
|
foreach (var (channelKey, days) in options.PerChannelRetentionDays)
|
|
{
|
|
builder.RequireThat(
|
|
Enum.TryParse<AuditChannel>(channelKey, ignoreCase: false, out _),
|
|
$"AuditLog:{nameof(AuditLogOptions.PerChannelRetentionDays)} key '{channelKey}' " +
|
|
$"is not a recognized channel name. Valid keys: {string.Join(", ", Enum.GetNames<AuditChannel>())}.");
|
|
|
|
// Valid when days is within [MinRetentionDays, RetentionDays] inclusive.
|
|
// The lower bound matches the global RetentionDays floor; the upper bound
|
|
// is the configured global window (longer is meaningless — see remarks).
|
|
builder.RequireThat(
|
|
!(days < MinRetentionDays || days > options.RetentionDays),
|
|
$"AuditLog:{nameof(AuditLogOptions.PerChannelRetentionDays)}['{channelKey}'] ({days}) " +
|
|
$"must be in [{MinRetentionDays}, {nameof(AuditLogOptions.RetentionDays)}={options.RetentionDays}] days " +
|
|
"— a per-channel window must be shorter than or equal to the global retention window.");
|
|
}
|
|
}
|
|
}
|