feat(commons): DeviceConfigIntent — device-host extraction + normalization home (R2-11)

This commit is contained in:
Joseph Doherty
2026-07-13 10:39:54 -04:00
parent cd3f1270ee
commit b6633f7562
3 changed files with 94 additions and 1 deletions
@@ -0,0 +1,47 @@
using System.Text.Json;
namespace ZB.MOM.WW.OtOpcUa.Commons.Types;
/// <summary>
/// SINGLE SOURCE OF TRUTH for extracting + normalizing a <c>Device</c>'s connection host from its
/// schemaless <c>DeviceConfig</c> JSON. Shared by the live-compose seam (<c>AddressSpaceComposer</c>),
/// the artifact-decode seam (<c>DeploymentArtifact</c>), and the FixedTree-partition path
/// (<c>DriverHostActor</c>) so an <c>EquipmentNode.DeviceHost</c> and a driver-discovered device-host
/// folder segment for the same device compare byte-equal. Ported verbatim from the historical
/// <c>AddressSpaceComposer.TryExtractDeviceHost</c> / <c>NormalizeDeviceHost</c>.
/// </summary>
public static class DeviceConfigIntent
{
/// <summary>
/// Extract a <c>Device</c>'s connection host from its schemaless <c>DeviceConfig</c> JSON: the
/// top-level <c>"HostAddress"</c> string (e.g. <c>"10.201.31.5:8193"</c>). Returns
/// <see langword="null"/> when the config is blank, not a JSON object, has no string
/// <c>HostAddress</c>, or the value is blank/whitespace. Never throws. The returned host is
/// deterministically normalized via <see cref="NormalizeHost"/> (trim + lower-case).
/// </summary>
/// <param name="deviceConfigJson">The device's schemaless <c>DeviceConfig</c> JSON blob.</param>
/// <returns>The normalized device host, or <see langword="null"/> when absent/blank/unparseable.</returns>
public static string? TryExtractHost(string? deviceConfigJson)
{
if (string.IsNullOrWhiteSpace(deviceConfigJson)) return null;
try
{
using var doc = JsonDocument.Parse(deviceConfigJson);
if (doc.RootElement.ValueKind != JsonValueKind.Object) return null;
if (!doc.RootElement.TryGetProperty("HostAddress", out var hostEl)
|| hostEl.ValueKind != JsonValueKind.String) return null;
var raw = hostEl.GetString();
if (string.IsNullOrWhiteSpace(raw)) return null;
return NormalizeHost(raw);
}
catch (JsonException) { return null; }
}
/// <summary>
/// The single normalization authority for a device host: trims surrounding whitespace and
/// lower-cases (invariant). Idempotent (an already-normalized value is unchanged).
/// </summary>
/// <param name="host">The raw host string (non-null; a non-empty <c>HostAddress</c> or folder segment).</param>
/// <returns>The normalized host (trimmed + lower-cased).</returns>
public static string NormalizeHost(string host) => host.Trim().ToLowerInvariant();
}