fix(r2-06): GREEN adapter guard — TryCreate + named InvalidOperationException on bad ServerHistorian:Endpoint (06/S-11)

This commit is contained in:
Joseph Doherty
2026-07-13 09:52:07 -04:00
parent 3a049a13a4
commit 3108abc13b
2 changed files with 52 additions and 14 deletions
@@ -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,