fix(r2-06): GREEN adapter guard — TryCreate + named InvalidOperationException on bad ServerHistorian:Endpoint (06/S-11)
This commit is contained in:
@@ -4,43 +4,53 @@
|
||||
"tasks": [
|
||||
{
|
||||
"id": "T1",
|
||||
"subject": "RED: misconfig repro test — HistorianGatewayClientAdapter.Create with empty/malformed Endpoint must throw a named InvalidOperationException (currently UriFormatException; test MUST fail on current code)",
|
||||
"subject": "RED: misconfig repro test \u2014 HistorianGatewayClientAdapter.Create with empty/malformed Endpoint must throw a named InvalidOperationException (currently UriFormatException; test MUST fail on current code)",
|
||||
"status": "completed",
|
||||
"blockedBy": []
|
||||
},
|
||||
{
|
||||
"id": "T2",
|
||||
"subject": "GREEN: adapter guard — Uri.TryCreate + InvalidOperationException naming ServerHistorian:Endpoint in HistorianGatewayClientAdapter.Create (defense in depth)",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T1"]
|
||||
"subject": "GREEN: adapter guard \u2014 Uri.TryCreate + InvalidOperationException naming ServerHistorian:Endpoint in HistorianGatewayClientAdapter.Create (defense in depth)",
|
||||
"status": "completed",
|
||||
"blockedBy": [
|
||||
"T1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T3",
|
||||
"subject": "RED: ServerHistorianOptionsValidator unit tests (7 cases incl. the AlarmHistorian-enabled/ServerHistorian-disabled corner naming both sections)",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T1"]
|
||||
"blockedBy": [
|
||||
"T1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T4",
|
||||
"subject": "GREEN: implement ServerHistorianOptionsValidator (OptionsValidatorBase, ctor IConfiguration, consumed = Enabled || AlarmHistorian:Enabled, fail on empty/non-absolute/non-http(s) Endpoint)",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T3"]
|
||||
"blockedBy": [
|
||||
"T3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T5",
|
||||
"subject": "Wiring: AddValidatedOptions<ServerHistorianOptions, ServerHistorianOptionsValidator> in Program.cs hasDriver block + OptionsValidationException wiring test + residual alarm-only-mode startup warning",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T4"]
|
||||
"blockedBy": [
|
||||
"T4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T6",
|
||||
"subject": "Warning parity: malformed-endpoint warning in ServerHistorianOptions.Validate() + Runtime.Tests case; commit 1 (S-11) after T1-T6 green",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T4"]
|
||||
"blockedBy": [
|
||||
"T4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T7",
|
||||
"subject": "U-7 pin: GatewayTagProvisionerTests Boolean_definition_carries_explicit_Int1_presence (asserts HistorianDataType.Int1 AND HasDataType — 0.2.0 proto3-optional presence witness; pin, no RED phase)",
|
||||
"subject": "U-7 pin: GatewayTagProvisionerTests Boolean_definition_carries_explicit_Int1_presence (asserts HistorianDataType.Int1 AND HasDataType \u2014 0.2.0 proto3-optional presence witness; pin, no RED phase)",
|
||||
"status": "pending",
|
||||
"blockedBy": []
|
||||
},
|
||||
@@ -48,13 +58,17 @@
|
||||
"id": "T8",
|
||||
"subject": "U-7 live leg: HISTGW_BOOL_SANDBOX_TAG in GatewayLiveFixture + EnsureTags_boolean_sandbox_provisions_as_Int1 + EnsureTags_type_change_outcome_is_observable retype probe (skip-clean offline; verify all-skip run)",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T7"]
|
||||
"blockedBy": [
|
||||
"T7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T9",
|
||||
"subject": "U-7 docs: migration/assessment note (EnsureTags is upsert; pre-bump Float Booleans; three retype hypotheses; failed=N tally observability) in docs/Historian.md + CLAUDE.md HISTGW_BOOL_SANDBOX_TAG recipe update; commit 2 (U-7) after T7-T9",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T8"]
|
||||
"blockedBy": [
|
||||
"T8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T10",
|
||||
@@ -66,13 +80,20 @@
|
||||
"id": "T11",
|
||||
"subject": "Bookkeeping: update archreview/plans/STATUS.md + 06-gateway-integrations.md prior-finding rows for S-11/U-7/C-7 when the work lands",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T6", "T9", "T10"]
|
||||
"blockedBy": [
|
||||
"T6",
|
||||
"T9",
|
||||
"T10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "T12",
|
||||
"subject": "OPERATOR GATE: run Category=LiveIntegration on the VPN against a 0.2.0 gateway (all 6 tests, incl. Boolean EnsureTags + retype probe); record the observed retype policy back into docs/Historian.md + STATUS.md; also discharges 06/U-2's full documented run",
|
||||
"status": "pending",
|
||||
"blockedBy": ["T9", "T11"]
|
||||
"blockedBy": [
|
||||
"T9",
|
||||
"T11"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+18
-1
@@ -35,14 +35,31 @@ public sealed class HistorianGatewayClientAdapter : IHistorianGatewayClient, IDi
|
||||
/// <param name="options">The bound <c>ServerHistorian</c> configuration (endpoint, key, TLS posture).</param>
|
||||
/// <param name="loggerFactory">Logger factory threaded into the package client's channel diagnostics.</param>
|
||||
/// <returns>A ready-to-use adapter whose underlying channel has not yet dialed the gateway.</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <see cref="ServerHistorianOptions.Endpoint"/> is empty or not an absolute <c>http(s)</c> URI.
|
||||
/// Defense in depth (archreview 06/S-11): the Host's <c>ServerHistorianOptionsValidator</c> fails
|
||||
/// these configs at startup, but any caller that bypasses <c>ValidateOnStart</c> (tests, the live
|
||||
/// fixture, future non-Host wiring) still gets a named, config-key-carrying error here instead of
|
||||
/// the raw <see cref="UriFormatException"/> the bare <c>new Uri(...)</c> ctor throws.
|
||||
/// </exception>
|
||||
public static HistorianGatewayClientAdapter Create(ServerHistorianOptions options, ILoggerFactory loggerFactory)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(loggerFactory);
|
||||
|
||||
// Fail fast with a named, actionable error rather than letting `new Uri("")` throw a raw
|
||||
// UriFormatException deep in the factory (archreview 06/S-11). The Endpoint is not a secret,
|
||||
// so it is safe to echo; the ApiKey is never surfaced.
|
||||
if (!Uri.TryCreate(options.Endpoint, UriKind.Absolute, out var endpointUri)
|
||||
|| (endpointUri.Scheme != Uri.UriSchemeHttp && endpointUri.Scheme != Uri.UriSchemeHttps))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"ServerHistorian:Endpoint must be an absolute http(s) URI (e.g. https://host:5222); got '{options.Endpoint}'.");
|
||||
}
|
||||
|
||||
var clientOptions = new HistorianGatewayClientOptions
|
||||
{
|
||||
Endpoint = new Uri(options.Endpoint),
|
||||
Endpoint = endpointUri,
|
||||
ApiKey = options.ApiKey,
|
||||
UseTls = options.UseTls,
|
||||
CaCertificatePath = options.CaCertificatePath,
|
||||
|
||||
Reference in New Issue
Block a user