87ff00fd30
Phase 6 (per-cluster mesh split) needs nodes to carry a cluster-scoped role like cluster-SITE-A; RoleParser previously rejected anything outside the fixed admin/driver/dev set. Adds IsClusterRole/ ClusterIdFromRole plus well-known-role constants, and points the three duplicate "driver" string literals (RedundancyStateActor, ClusterNodeAddressReconcilerActor, ServiceCollectionExtensions) at the new RoleParser.Driver so the value has one source, without renaming any existing DriverRole symbol. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
59 lines
2.5 KiB
C#
59 lines
2.5 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Cluster;
|
|
|
|
public static class RoleParser
|
|
{
|
|
/// <summary>The admin-role name.</summary>
|
|
public const string Admin = "admin";
|
|
|
|
/// <summary>The driver-role name. Single source of truth — downstream <c>DriverRole</c>
|
|
/// constants (<c>RedundancyStateActor</c>, <c>ClusterNodeAddressReconcilerActor</c>,
|
|
/// <c>ServiceCollectionExtensions</c>) alias this value rather than duplicating the literal.</summary>
|
|
public const string Driver = "driver";
|
|
|
|
/// <summary>The dev-role name.</summary>
|
|
public const string Dev = "dev";
|
|
|
|
/// <summary>Prefix identifying a cluster-scoped role, e.g. <c>cluster-SITE-A</c> (per-cluster mesh, Phase 6).</summary>
|
|
public const string ClusterRolePrefix = "cluster-";
|
|
|
|
private static readonly HashSet<string> Allowed = new(StringComparer.Ordinal)
|
|
{
|
|
Admin, Driver, Dev,
|
|
};
|
|
|
|
/// <summary>Returns true when <paramref name="role"/> is a cluster-scoped role — starts with
|
|
/// <see cref="ClusterRolePrefix"/> and carries a non-empty ClusterId suffix.</summary>
|
|
/// <param name="role">The role name to test.</param>
|
|
public static bool IsClusterRole(string role) =>
|
|
role.StartsWith(ClusterRolePrefix, StringComparison.Ordinal) && role.Length > ClusterRolePrefix.Length;
|
|
|
|
/// <summary>Extracts the ClusterId from a cluster-scoped role, e.g. <c>cluster-SITE-A</c> → <c>SITE-A</c>.
|
|
/// Assumes <see cref="IsClusterRole"/> is true for <paramref name="role"/>.</summary>
|
|
/// <param name="role">The cluster-scoped role name.</param>
|
|
public static string ClusterIdFromRole(string role) => role[ClusterRolePrefix.Length..];
|
|
|
|
/// <summary>Parses a comma-separated string of role names into a validated array.</summary>
|
|
/// <param name="raw">The raw role string to parse.</param>
|
|
/// <returns>The distinct, lower-cased, validated role names.</returns>
|
|
public static string[] Parse(string? raw)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(raw)) return Array.Empty<string>();
|
|
|
|
var roles = raw
|
|
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Select(r => r.ToLowerInvariant())
|
|
.Distinct()
|
|
.ToArray();
|
|
|
|
foreach (var r in roles)
|
|
{
|
|
if (!Allowed.Contains(r) && !IsClusterRole(r))
|
|
throw new ArgumentException(
|
|
$"Unknown role '{r}'. Allowed: {string.Join(", ", Allowed)}, or '{ClusterRolePrefix}<ClusterId>'.",
|
|
nameof(raw));
|
|
}
|
|
|
|
return roles;
|
|
}
|
|
}
|