using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
using ZB.MOM.WW.Secrets.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy;
///
/// Static factory registration helper for . Mirrors
/// GalaxyProxyDriverFactoryExtensions / ModbusDriverFactoryExtensions.
/// Server's Program.cs calls once at startup; the driver
/// bootstrap pipeline materialises DriverInstance rows whose DriverType matches
/// into live instances.
///
///
/// "GalaxyMxGateway" is the sole Galaxy driver-type name. The legacy
/// "Galaxy" proxy type (retired in PR 7.2) no longer exists; there is no
/// server-side backend switch — every Galaxy DriverInstance row uses this type name.
///
public static class GalaxyDriverFactoryExtensions
{
public const string DriverTypeName = "GalaxyMxGateway";
/// Registers the Galaxy driver factory with the given registry, secret resolver, and optional logger factory.
/// The driver factory registry to register with.
///
/// The shared secret resolver (from DI) used to resolve the secret: arm of
/// each instance's Gateway.ApiKeySecretRef.
///
/// The optional logger factory for creating drivers.
public static void Register(
DriverFactoryRegistry registry,
ISecretResolver secretResolver,
ILoggerFactory? loggerFactory = null)
{
ArgumentNullException.ThrowIfNull(registry);
ArgumentNullException.ThrowIfNull(secretResolver);
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, secretResolver));
}
///
/// Convenience for tests + standalone callers. Uses the
/// null-object, so the secret: API-key arm resolves fail-closed (absent) — callers
/// that need a working secret: ref must use the path that
/// threads the DI resolver.
///
/// The unique identifier for the driver instance.
/// The driver configuration in JSON format.
/// The constructed Galaxy driver instance.
public static GalaxyDriver CreateInstance(string driverInstanceId, string driverConfigJson)
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null, NullSecretResolver.Instance);
/// Creates a Galaxy driver instance from configuration JSON with optional logger factory and a secret resolver.
/// The unique identifier for the driver instance.
/// The driver configuration in JSON format.
/// The optional logger factory for creating drivers.
/// The secret resolver for the driver's secret: API-key arm.
/// The constructed Galaxy driver instance.
public static GalaxyDriver CreateInstance(
string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory, ISecretResolver secretResolver)
{
ArgumentNullException.ThrowIfNull(secretResolver);
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
var dto = JsonSerializer.Deserialize(driverConfigJson, JsonOptions)
?? throw new InvalidOperationException(
$"Galaxy driver config for '{driverInstanceId}' deserialised to null");
var options = new GalaxyDriverOptions(
Gateway: new GalaxyGatewayOptions(
Endpoint: dto.Gateway?.Endpoint
?? throw new InvalidOperationException(
$"Galaxy driver '{driverInstanceId}' missing required Gateway.Endpoint"),
ApiKeySecretRef: dto.Gateway.ApiKeySecretRef
?? throw new InvalidOperationException(
$"Galaxy driver '{driverInstanceId}' missing required Gateway.ApiKeySecretRef"),
UseTls: dto.Gateway.UseTls ?? true,
CaCertificatePath: dto.Gateway.CaCertificatePath,
ConnectTimeoutSeconds: dto.Gateway.ConnectTimeoutSeconds ?? 10,
DefaultCallTimeoutSeconds: dto.Gateway.DefaultCallTimeoutSeconds ?? 30,
StreamTimeoutSeconds: dto.Gateway.StreamTimeoutSeconds ?? 0),
MxAccess: new GalaxyMxAccessOptions(
ClientName: dto.MxAccess?.ClientName
?? throw new InvalidOperationException(
$"Galaxy driver '{driverInstanceId}' missing required MxAccess.ClientName"),
PublishingIntervalMs: dto.MxAccess.PublishingIntervalMs ?? 1000,
WriteUserId: dto.MxAccess.WriteUserId ?? 0,
EventPumpChannelCapacity: dto.MxAccess.EventPumpChannelCapacity ?? 50_000),
Repository: new GalaxyRepositoryOptions(
DiscoverPageSize: dto.Repository?.DiscoverPageSize ?? 5000,
WatchDeployEvents: dto.Repository?.WatchDeployEvents ?? true),
Reconnect: new GalaxyReconnectOptions(
InitialBackoffMs: dto.Reconnect?.InitialBackoffMs ?? 500,
MaxBackoffMs: dto.Reconnect?.MaxBackoffMs ?? 30_000,
ReplayOnSessionLost: dto.Reconnect?.ReplayOnSessionLost ?? true))
{
// v3: the deploy artifact delivers the authored raw tags (RawPath identity + TagConfig blob
// carrying the camelCase "attributeRef" + WriteIdempotent flag). The driver builds its
// RawPath → attributeRef table from these. Empty when the config omits them.
RawTags = dto.RawTags ?? [],
};
return new GalaxyDriver(driverInstanceId, options, secretResolver, loggerFactory?.CreateLogger());
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
/// Data transfer object for Galaxy driver configuration JSON.
internal sealed class GalaxyDriverConfigDto
{
/// Gets or sets the gateway configuration.
public GatewayDto? Gateway { get; init; }
/// Gets or sets the MX Access configuration.
public MxAccessDto? MxAccess { get; init; }
/// Gets or sets the repository configuration.
public RepositoryDto? Repository { get; init; }
/// Gets or sets the reconnect configuration.
public ReconnectDto? Reconnect { get; init; }
///
/// Gets or sets the v3 authored raw tags. Each carries the RawPath
/// identity, the driver TagConfig blob (with the Galaxy attributeRef), and the
/// WriteIdempotent flag. Bound straight through to .
///
public List? RawTags { get; init; }
}
/// Gateway configuration section.
internal sealed class GatewayDto
{
/// Gets or sets the gateway endpoint address.
public string? Endpoint { get; init; }
/// Gets or sets the API key secret reference.
public string? ApiKeySecretRef { get; init; }
/// Gets or sets whether to use TLS.
public bool? UseTls { get; init; }
/// Gets or sets the CA certificate path.
public string? CaCertificatePath { get; init; }
/// Gets or sets the connection timeout in seconds.
public int? ConnectTimeoutSeconds { get; init; }
/// Gets or sets the default call timeout in seconds.
public int? DefaultCallTimeoutSeconds { get; init; }
/// Gets or sets the stream timeout in seconds.
public int? StreamTimeoutSeconds { get; init; }
}
/// MX Access configuration section.
internal sealed class MxAccessDto
{
/// Gets or sets the client name.
public string? ClientName { get; init; }
/// Gets or sets the publishing interval in milliseconds.
public int? PublishingIntervalMs { get; init; }
/// Gets or sets the write user ID.
public int? WriteUserId { get; init; }
/// Gets or sets the event pump channel capacity.
public int? EventPumpChannelCapacity { get; init; }
}
/// Repository configuration section.
internal sealed class RepositoryDto
{
/// Gets or sets the discover page size.
public int? DiscoverPageSize { get; init; }
/// Gets or sets whether to watch deploy events.
public bool? WatchDeployEvents { get; init; }
}
/// Reconnect configuration section.
internal sealed class ReconnectDto
{
/// Gets or sets the initial backoff in milliseconds.
public int? InitialBackoffMs { get; init; }
/// Gets or sets the maximum backoff in milliseconds.
public int? MaxBackoffMs { get; init; }
/// Gets or sets whether to replay on session lost.
public bool? ReplayOnSessionLost { get; init; }
}
}