using System.Text.Json; using System.Text.Json.Serialization; using MeterVault.Infrastructure.Security; namespace MeterVault.Infrastructure.Ingestion; /// /// The parsed JSON for a Home Assistant /// connection (SDD §6.2). The long-lived token is never held here as plaintext (SDD §6.4): either /// names an environment variable resolved at runtime, or /// holds it encrypted under the app's data-protection key ring. /// public sealed record HaEndpointConfig { private static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; /// Base URL of the Home Assistant instance, e.g. http://homeassistant.local:8123. public string? BaseUrl { get; init; } /// Name of the environment variable holding the long-lived access token. public string? TokenEnv { get; init; } /// /// The long-lived access token, encrypted by . Set when /// the operator typed the token into the admin UI instead of naming an environment variable. /// public string? TokenEnc { get; init; } /// /// When true, a persistent WebSocket subscription pushes state changes in real time /// (); when false (default) the REST poll worker /// samples on each source's interval. An endpoint is handled by exactly one of the two. /// public bool UseWebSocket { get; init; } public static HaEndpointConfig Parse(string? json) { if (string.IsNullOrWhiteSpace(json)) { return new HaEndpointConfig(); } try { return JsonSerializer.Deserialize(json, Options) ?? new HaEndpointConfig(); } catch (JsonException) { return new HaEndpointConfig(); } } public string ToJson() => JsonSerializer.Serialize(this, Options); /// /// Resolves the token: the encrypted value when one was entered directly, otherwise the /// referenced environment variable. Null when neither yields anything. /// public string? ResolveToken(SecretProtector? protector = null) => EndpointSecret.Resolve(TokenEnc, TokenEnv, protector); }