Files
scadaproj/components/configuration/current-state/mxaccessgw/CURRENT-STATE.md
T

123 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Configuration validation — current state: MxAccessGateway
Repo: `~/Desktop/MxAccessGateway` (`mxaccessgw`). Stack: .NET 10 gateway (x64) + .NET 4.8 worker
(x86), gRPC; solution `src/MxGateway.sln`. All paths relative to repo root. Verified 2026-06-01.
MxGateway has **one large, well-structured options validator** for a single composite
`GatewayOptions`, wired through a bespoke DI extension. It is the textbook hand-rolled version of
exactly what the shared library normalizes: a private `List<string>` accumulator, a stack of
`AddIfXxx` helper methods, and an `AddOptions().BindConfiguration().ValidateOnStart()` registration
— all of which collapse onto `OptionsValidatorBase` + `AddValidatedOptions` with the domain rules
left untouched.
## 1. `GatewayOptionsValidator` — hand-rolled `IValidateOptions<GatewayOptions>`
`src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayOptionsValidator.cs`:
- `:6``public sealed class GatewayOptionsValidator : IValidateOptions<GatewayOptions>`
implements the interface directly (no shared base).
- `:1734``Validate(string? name, GatewayOptions options)`: creates `List<string> failures`
(`:19`), dispatches to **nine** sub-validators (`:2129`), and returns the
`failures.Count == 0 ? Success : Fail(failures)` tail (`:3133`). This is precisely the
accumulate-all-then-decide convention the base owns.
- Sub-validators (each takes `(section options, List<string> failures)` and `failures.Add(...)`s):
- `:36` `ValidateAuthentication``Enum.IsDefined` on `Mode`; conditional required
`SqlitePath` / `PepperSecretName` when `Mode == ApiKey`.
- `:61` `ValidateLdap` — short-circuits when `!Enabled` (`:63`); seven required-string checks
(`:6889`), `Port` positivity (`:90`), and a cross-field `UseTls`/`AllowInsecureLdap` rule (`:92`).
- `:98` `ValidateWorker` — required `ExecutablePath` (`:100`), valid-path + `.exe`-extension
checks (`:101110`), `Enum.IsDefined` on architecture (`:120`), eight positive-int checks
(`:125152`), and a cross-field `HeartbeatGraceSeconds >= HeartbeatIntervalSeconds` rule
(`:154`), plus a `MaxMessageBytes` range (`:160`).
- `:167` `ValidateSessions` — five positive-int checks (`:169185`) + an "unsupported feature"
guard (`:187`).
- `:194` `ValidateEvents``QueueCapacity` positivity (`:196`) + `Enum.IsDefined` on policy (`:198`).
- `:204` `ValidateDashboard``GroupToRole` map shape (`:211224`) + interval/limit bounds (`:226237`).
- `:240` `ValidateAlarms` — short-circuits when `!Enabled` (`:242`); a "need expression or area"
rule (`:251`) + a canonical-prefix rule (`:258`).
- `:269` `ValidateTls``ValidityYears` range (`:271`), required non-blank cert path + valid path
(`:278285`), non-blank DNS-name entries (`:287`).
- `:296` `ValidateProtocol` — exact `WorkerProtocolVersion` match (`:298`) + `MaxGrpcMessageBytes`
range (`:304`).
- **Private helpers that duplicate the shared primitives** (`:311358`):
- `:311` `AddIfBlank` → maps to `ValidationBuilder.Required`.
- `:319` `AddIfNotPositive` → maps to `RequireThat(value > 0, ...)`.
- `:327` `AddIfNegative` → maps to `RequireThat(value >= 0, ...)`.
- `:335` `AddIfInvalidPath` → app-specific; stays as a `RequireThat`/`Add` custom rule
(filesystem-path validity is not a shared primitive).
Every failure message is the gateway's own (`"MxGateway:<Section>:<Field> ..."`) — these are
**domain rules and stay per-project**; only the accumulation plumbing and the trivial helpers move
to the base.
## 2. DI wiring — `AddGatewayConfiguration`
`src/ZB.MOM.WW.MxGateway.Server/Configuration/GatewayConfigurationServiceCollectionExtensions.cs`:
- `:1021``AddGatewayConfiguration(this IServiceCollection services)`:
- `:1215``services.AddOptions<GatewayOptions>().BindConfiguration(GatewayOptions.SectionName).ValidateOnStart();`
- `:17``services.AddSingleton<IValidateOptions<GatewayOptions>, GatewayOptionsValidator>();`
- `:18` — also registers `IGatewayConfigurationProvider` (a separate concern; stays).
Lines `:1217` are exactly the `bind + register-validator + ValidateOnStart` triple that
`AddValidatedOptions<GatewayOptions, GatewayOptionsValidator>` collapses into one call. (One nuance:
the gateway uses `BindConfiguration(SectionName)` — which reads the section path off the type/const
— whereas `AddValidatedOptions` takes an explicit `sectionPath` string; the adoption passes
`GatewayOptions.SectionName` as that argument.)
A bad `MxGateway` section surfaces as **`OptionsValidationException`** at host start, via
`ValidateOnStart()` — the same path `AddValidatedOptions` produces.
## 3. Summary
| Surface | What exists | Shared-lib mapping |
|---|---|---|
| Options validator | `GatewayOptionsValidator : IValidateOptions<GatewayOptions>` (~360 LOC, 9 sub-validators) | → `OptionsValidatorBase<GatewayOptions>` |
| Failure accumulation | private `List<string> failures` + `Count == 0 ? Success : Fail` tail | → owned by base + `ValidationBuilder` |
| Rule helpers | `AddIfBlank` / `AddIfNotPositive` / `AddIfNegative` | → `Required` / `RequireThat` primitives |
| App-specific helper | `AddIfInvalidPath` | → stays as a custom `RequireThat`/`Add` rule |
| DI wiring | `AddGatewayConfiguration` (`AddOptions().BindConfiguration().ValidateOnStart()` + `AddSingleton<IValidateOptions...>`) | → `AddValidatedOptions<GatewayOptions, GatewayOptionsValidator>` |
| Pre-host preflight | none (single host, no pre-Akka stage) | n/a — `ConfigPreflight` not needed |
---
## Adoption plan → `ZB.MOM.WW.Configuration`
**Migrate the validator to the shared base:**
- Change `GatewayOptionsValidator : IValidateOptions<GatewayOptions>`
`GatewayOptionsValidator : OptionsValidatorBase<GatewayOptions>`
(`GatewayOptionsValidator.cs:6`).
- Replace the public `Validate(string? name, GatewayOptions options)` (`:17`) with the
`protected override void Validate(ValidationBuilder v, GatewayOptions options)`. Delete the
`List<string> failures` and the `Count == 0 ? Success : Fail` tail (`:19`, `:3133`) — the base
supplies both.
- Keep the nine sub-validators but re-thread them to take the `ValidationBuilder` instead of
`List<string>`. Map the helpers: `AddIfBlank``v.Required(...)`, `AddIfNotPositive(x, msg)`
`v.RequireThat(x > 0, msg)`, `AddIfNegative(x, msg)``v.RequireThat(x >= 0, msg)`. Keep
`AddIfInvalidPath` as a private helper that records via `v.Add(...)` (filesystem-path validity is
app-specific; not a shared primitive). **All gateway message strings are preserved verbatim**
domain rules do not change.
**Migrate the DI wiring:**
- In `AddGatewayConfiguration` (`GatewayConfigurationServiceCollectionExtensions.cs:1217`),
replace the `AddOptions().BindConfiguration().ValidateOnStart()` + `AddSingleton<IValidateOptions...>`
pair with:
```csharp
services.AddValidatedOptions<GatewayOptions, GatewayOptionsValidator>(
configuration, GatewayOptions.SectionName);
```
(The extension gains an `IConfiguration` parameter, or resolves it from the builder, since
`AddValidatedOptions` binds from an explicit `IConfiguration` rather than the ambient
`BindConfiguration`.) The `IGatewayConfigurationProvider` registration (`:18`) is unrelated and
stays.
**Keep bespoke (unchanged):**
- Every `"MxGateway:<Section>:<Field>"` message and every domain rule (worker `.exe` extension,
heartbeat grace ≥ interval, protocol-version exact match, `\\`-prefixed alarm expression, etc.).
- `GatewayOptions` and its section types — these are MxGateway's options classes; not shared.
- The net48 x86 worker — does no `IConfiguration` validation; excluded entirely.
**Status:** follow-on (tracked in [`../GAPS.md`](../GAPS.md)). Medium-weight, low-risk —
behaviour-preserving plumbing swap; one validator, one DI extension.