namespace ZB.MOM.WW.OtOpcUa.Cluster;
public static class RoleParser
{
/// The admin-role name.
public const string Admin = "admin";
/// The driver-role name. Single source of truth — downstream DriverRole
/// constants (RedundancyStateActor, ClusterNodeAddressReconcilerActor,
/// ServiceCollectionExtensions) alias this value rather than duplicating the literal.
public const string Driver = "driver";
/// The dev-role name.
public const string Dev = "dev";
/// Prefix identifying a cluster-scoped role, e.g. cluster-SITE-A (per-cluster mesh, Phase 6).
public const string ClusterRolePrefix = "cluster-";
private static readonly HashSet Allowed = new(StringComparer.Ordinal)
{
Admin, Driver, Dev,
};
/// Returns true when is a cluster-scoped role — starts with
/// and carries a non-empty ClusterId suffix.
/// The role name to test.
public static bool IsClusterRole(string role) =>
role.StartsWith(ClusterRolePrefix, StringComparison.Ordinal) && role.Length > ClusterRolePrefix.Length;
/// Extracts the ClusterId from a cluster-scoped role, e.g. cluster-SITE-A → SITE-A.
/// Assumes is true for .
/// The cluster-scoped role name.
public static string ClusterIdFromRole(string role) => role[ClusterRolePrefix.Length..];
/// Parses a comma-separated string of role names into a validated array.
/// The raw role string to parse.
/// The distinct, lower-cased, validated role names.
public static string[] Parse(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return Array.Empty();
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}'.",
nameof(raw));
}
return roles;
}
}