diff --git a/ZB.MOM.WW.Configuration/CLAUDE.md b/ZB.MOM.WW.Configuration/CLAUDE.md new file mode 100644 index 0000000..818b340 --- /dev/null +++ b/ZB.MOM.WW.Configuration/CLAUDE.md @@ -0,0 +1,76 @@ +# ZB.MOM.WW.Configuration + +Startup configuration-validation library for the **ZB.MOM.WW SCADA family** (OtOpcUa, MxAccessGateway, ScadaBridge). These are **libraries, not a service** — the package is linked directly into the consuming application at build time. There is no central validation process; all validation runs in-process at startup. + +The library normalizes the three-project configuration-validation surface: a failure-accumulating `IValidateOptions` base, reusable rule primitives, a bind+validate+`ValidateOnStart` DI extension, and a pre-host `ConfigPreflight` aggregator for raw `IConfiguration` — so the plumbing is written once and domain rules stay per-project. + +**Built at 0.1.0. Not yet adopted by OtOpcUa, MxAccessGateway, or ScadaBridge.** Adoption tracked in `~/Desktop/scadaproj/components/configuration/GAPS.md`. + +--- + +## Package + +| Package | Responsibilities | Key Dependencies | +|---|---|---| +| `ZB.MOM.WW.Configuration` | `OptionsValidatorBase` (abstract `IValidateOptions` base, failure-accumulating), `ValidationBuilder` (rule primitives: `Required`, `Port`, `HostPort`, `PositiveTimeSpan`, `OneOf`, `MinCount`, `RequireThat`, `Add`), `ServiceCollectionExtensions.AddValidatedOptions` (bind + validator + `ValidateOnStart` in one call), `ConfigPreflight` (fluent pre-host raw-`IConfiguration` checker). | `Microsoft.Extensions.Options`, `Microsoft.Extensions.Options.ConfigurationExtensions`, `Microsoft.Extensions.Configuration.Abstractions`, `Microsoft.Extensions.DependencyInjection.Abstractions` | + +Single package; no ASP.NET Core framework reference. + +--- + +## Build, test, and pack commands + +```bash +# From ZB.MOM.WW.Configuration/ + +# Build +dotnet build ZB.MOM.WW.Configuration.slnx + +# Test (no external dependencies required) +dotnet test ZB.MOM.WW.Configuration.slnx + +# Pack (one .nupkg lands in artifacts/) +dotnet pack ZB.MOM.WW.Configuration.slnx -c Release -o ./artifacts +``` + +Test breakdown: + +| Assembly | Tests | +|---|---| +| `ZB.MOM.WW.Configuration.Tests` | 27 | +| **Total** | **27** | + +`GeneratePackageOnBuild` is off — pack explicitly with the command above. + +--- + +## Source layout + +``` +ZB.MOM.WW.Configuration/ +├── Directory.Build.props # version (0.1.0), TFM (net10.0), central package mgmt +├── Directory.Packages.props # pinned package versions +├── ZB.MOM.WW.Configuration.slnx # solution file +├── src/ +│ └── ZB.MOM.WW.Configuration/ # library project +│ ├── OptionsValidatorBase.cs +│ ├── ValidationBuilder.cs +│ ├── ServiceCollectionExtensions.cs +│ └── ConfigPreflight.cs +└── tests/ + └── ZB.MOM.WW.Configuration.Tests/ # xUnit test project (27 tests) +``` + +--- + +## Status + +Part of the **scadaproj component-normalization family** — this is the configuration + validation component. Built at **0.1.0**. **Not yet adopted by OtOpcUa, MxAccessGateway, or ScadaBridge** — follow-on adoption is tracked in: + +- `~/Desktop/scadaproj/components/configuration/GAPS.md` + +Design documentation: + +- `~/Desktop/scadaproj/components/configuration/spec/SPEC.md` — normalized validation target +- `~/Desktop/scadaproj/components/configuration/shared-contract/ZB.MOM.WW.Configuration.md` — proposed shared-library API +- `~/Desktop/scadaproj/components/configuration/current-state/` — per-project current state (code-verified) diff --git a/ZB.MOM.WW.Configuration/README.md b/ZB.MOM.WW.Configuration/README.md new file mode 100644 index 0000000..b2eccf6 --- /dev/null +++ b/ZB.MOM.WW.Configuration/README.md @@ -0,0 +1,112 @@ +# ZB.MOM.WW.Configuration + +Startup configuration-validation library for the **ZB.MOM.WW SCADA family** (OtOpcUa, MxAccessGateway, ScadaBridge). This is a **library, not a service** — the package is linked directly into the consuming application at build time. It extracts the `IValidateOptions` plumbing the three apps share — failure accumulation, rule primitives, bind+validate DI wiring, and pre-host preflight — so that domain-specific validation rules stay per-project and the boilerplate does not drift. + +--- + +## What's in the box + +| Type | Description | +|---|---| +| `OptionsValidatorBase` | Abstract `IValidateOptions`. Override `protected void Validate(ValidationBuilder v, TOptions o)` to declare failures; the base aggregates all failures and returns a single `ValidateOptionsResult`. | +| `ValidationBuilder` | Failure accumulator. Primitives: `Required`, `Port`, `HostPort`, `PositiveTimeSpan`, `OneOf`, `MinCount`, `RequireThat(bool, msg)`, `Add(msg)`. Properties: `Failures` (read), `IsValid`. | +| `ServiceCollectionExtensions` | `AddValidatedOptions(IConfiguration config, string sectionPath)` — binds the section, registers the validator, and calls `ValidateOnStart()` in a single extension method. Returns `OptionsBuilder`. | +| `ConfigPreflight` | Pre-host raw-`IConfiguration` checker. Fluent API: `For(config)`, `.Require(key, predicate, reason)`, `.RequireValue(key)`, `.RequirePort(key)`, `.When(cond, block)`, `.ThrowIfInvalid()`. | + +--- + +## Usage + +### 1. Validator subclass + +```csharp +public sealed class ClusterOptionsValidator : OptionsValidatorBase +{ + protected override void Validate(ValidationBuilder v, ClusterOptions o) + { + v.MinCount(o.SeedNodes, 2, "Cluster:SeedNodes"); + v.OneOf(o.Strategy, new[] { "keep-oldest" }, "Cluster:Strategy"); + v.PositiveTimeSpan(o.StableAfter, "Cluster:StableAfter"); + } +} +``` + +### 2. DI wiring + +```csharp +builder.Services.AddValidatedOptions( + builder.Configuration, "ScadaBridge:Cluster"); +``` + +This binds `ScadaBridge:Cluster`, registers `ClusterOptionsValidator`, and enables `ValidateOnStart` — the app refuses to start if the section fails validation. + +### 3. Pre-host preflight + +```csharp +ConfigPreflight.For(configuration) + .Require("Node:Role", v => v is "Central" or "Site", "must be 'Central' or 'Site'") + .RequirePort("Node:RemotingPort") + .When(role == "Site", p => p.RequireValue("Node:SiteId")) + .ThrowIfInvalid(); +``` + +Use `ConfigPreflight` before `WebApplication.CreateBuilder` for critical keys (node role, remoting port, site ID) that must be present and valid before the DI container is even constructed. + +--- + +## Building and testing + +```bash +# from ZB.MOM.WW.Configuration/ +dotnet test ZB.MOM.WW.Configuration.slnx +``` + +All tests run with no external dependencies: + +| Assembly | Tests | +|---|---| +| `ZB.MOM.WW.Configuration.Tests` | 27 | +| **Total** | **27** | + +--- + +## Packing + +```bash +dotnet pack ZB.MOM.WW.Configuration.slnx -c Release -o ./artifacts +``` + +Produces one `.nupkg` file in `artifacts/`: + +``` +ZB.MOM.WW.Configuration.0.1.0.nupkg +``` + +`GeneratePackageOnBuild` is off — pack explicitly as above. Version is set in `Directory.Build.props`. + +--- + +## Dependencies + +The package has a minimal closure — only `Microsoft.Extensions.*` abstractions: + +- `Microsoft.Extensions.Options` +- `Microsoft.Extensions.Options.ConfigurationExtensions` +- `Microsoft.Extensions.Configuration.Abstractions` +- `Microsoft.Extensions.DependencyInjection.Abstractions` + +No third-party packages; no ASP.NET Core framework reference. + +--- + +## Status + +**Built at 0.1.0. Not yet adopted by the three apps.** Adoption is tracked in the component backlog: + +- `~/Desktop/scadaproj/components/configuration/GAPS.md` + +Design documentation lives alongside that backlog: + +- `~/Desktop/scadaproj/components/configuration/spec/SPEC.md` — normalized validation target +- `~/Desktop/scadaproj/components/configuration/shared-contract/ZB.MOM.WW.Configuration.md` — proposed API +- `~/Desktop/scadaproj/components/configuration/current-state/` — per-project current state (code-verified)