89043cb2b6
Client.Dotnet-004: documented DefaultCallTimeout as both the per-attempt deadline and the shared retry budget, and removed DeadlineExceeded from the transient-retry set (a client-imposed deadline cannot be helped by retrying). Client.Dotnet-005: RegisterAsync/AddItemAsync/AddItem2Async silently returned 0 when a successful reply lacked the typed payload. They now throw a descriptive MxGatewayException. Client.Dotnet-006: added XML docs to the previously undocumented public members MaxGrpcMessageBytes, GatewayProtocolVersion, WorkerProtocolVersion. Client.Dotnet-007: corrected the AcknowledgeAlarmAsync XML comment — the RPC requires the admin scope, not a non-existent invoke:alarm-ack sub-scope. Client.Dotnet-008: the CLI redactor missed env-var-sourced keys because the caller passed only the --api-key option. Redaction now uses the same resolver, stripping env-var keys too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
4.7 KiB
C#
141 lines
4.7 KiB
C#
using Microsoft.Extensions.Logging;
|
|
|
|
namespace MxGateway.Client;
|
|
|
|
/// <summary>
|
|
/// Configures the gRPC channel used by the .NET MXAccess Gateway client.
|
|
/// </summary>
|
|
public sealed class MxGatewayClientOptions
|
|
{
|
|
/// <summary>
|
|
/// Gets the gateway endpoint URI (required).
|
|
/// </summary>
|
|
public required Uri Endpoint { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the API key for gateway authentication (required).
|
|
/// </summary>
|
|
public required string ApiKey { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether to use TLS for the gateway connection.
|
|
/// </summary>
|
|
public bool UseTls { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the path to a CA certificate file for custom certificate validation.
|
|
/// </summary>
|
|
public string? CaCertificatePath { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the server name override for SNI during TLS handshake.
|
|
/// </summary>
|
|
public string? ServerNameOverride { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the timeout for establishing connection to the gateway.
|
|
/// </summary>
|
|
public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(10);
|
|
|
|
/// <summary>
|
|
/// Gets the timeout budget for a unary gRPC operation. This is both the gRPC
|
|
/// deadline stamped on each individual attempt and the overall budget for the
|
|
/// whole safe-unary operation: for retryable calls the initial attempt, every
|
|
/// retry, and the backoff delays between them all share this single budget.
|
|
/// It is therefore an upper bound on the total wall-clock time a safe-unary
|
|
/// call can take, not a fresh per-retry allowance.
|
|
/// </summary>
|
|
public TimeSpan DefaultCallTimeout { get; init; } = TimeSpan.FromSeconds(30);
|
|
|
|
/// <summary>
|
|
/// Gets the optional timeout for streaming gRPC calls.
|
|
/// </summary>
|
|
public TimeSpan? StreamTimeout { get; init; }
|
|
|
|
/// <summary>
|
|
/// Gets the maximum size, in bytes, of a single gRPC message the client will
|
|
/// send or receive. Applied to both the send and receive limits of the
|
|
/// underlying channel. Defaults to 16 MiB.
|
|
/// </summary>
|
|
public int MaxGrpcMessageBytes { get; init; } = 16 * 1024 * 1024;
|
|
|
|
/// <summary>
|
|
/// Gets the retry configuration for safe unary calls.
|
|
/// </summary>
|
|
public MxGatewayClientRetryOptions Retry { get; init; } = new();
|
|
|
|
/// <summary>
|
|
/// Gets the logger factory for diagnostic logging.
|
|
/// </summary>
|
|
public ILoggerFactory? LoggerFactory { get; init; }
|
|
|
|
/// <summary>
|
|
/// Validates the client options for consistency and correctness.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentNullException">Endpoint is null.</exception>
|
|
/// <exception cref="ArgumentException">Options are invalid or inconsistent.</exception>
|
|
/// <exception cref="ArgumentOutOfRangeException">Timeout values are not greater than zero.</exception>
|
|
public void Validate()
|
|
{
|
|
ArgumentNullException.ThrowIfNull(Endpoint);
|
|
|
|
if (!Endpoint.IsAbsoluteUri)
|
|
{
|
|
throw new ArgumentException(
|
|
"The gateway endpoint must be an absolute URI.",
|
|
nameof(Endpoint));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(ApiKey))
|
|
{
|
|
throw new ArgumentException(
|
|
"The gateway API key must not be empty.",
|
|
nameof(ApiKey));
|
|
}
|
|
|
|
if (ConnectTimeout <= TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(ConnectTimeout),
|
|
"The connect timeout must be greater than zero.");
|
|
}
|
|
|
|
if (DefaultCallTimeout <= TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(DefaultCallTimeout),
|
|
"The default call timeout must be greater than zero.");
|
|
}
|
|
|
|
if (StreamTimeout is not null && StreamTimeout <= TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(StreamTimeout),
|
|
"The stream timeout must be greater than zero when configured.");
|
|
}
|
|
|
|
if (MaxGrpcMessageBytes <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(MaxGrpcMessageBytes),
|
|
"The maximum gRPC message size must be greater than zero.");
|
|
}
|
|
|
|
if (UseTls && Endpoint.Scheme != Uri.UriSchemeHttps)
|
|
{
|
|
throw new ArgumentException(
|
|
"UseTls requires an https gateway endpoint.",
|
|
nameof(Endpoint));
|
|
}
|
|
|
|
if (!UseTls && Endpoint.Scheme == Uri.UriSchemeHttps)
|
|
{
|
|
throw new ArgumentException(
|
|
"An https gateway endpoint requires UseTls.",
|
|
nameof(Endpoint));
|
|
}
|
|
|
|
Retry.Validate();
|
|
}
|
|
}
|