64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
80 lines
3.7 KiB
C#
80 lines
3.7 KiB
C#
using System.Text.RegularExpressions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;
|
|
|
|
/// <summary>
|
|
/// Per Phase 7 plan decision #13, alarm messages are static-with-substitution
|
|
/// templates. The engine resolves <c>{TagPath}</c> tokens at event emission time
|
|
/// against current tag values; unresolvable tokens become <c>{?}</c> so the event
|
|
/// still fires but the operator sees where the reference broke.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Token syntax: <c>{path/with/slashes}</c>. Brace-stripped the contents must
|
|
/// match a path the caller's resolver function can look up. No escaping
|
|
/// currently — if you need literal braces in the message, reach for a feature
|
|
/// request.
|
|
/// </para>
|
|
/// <para>
|
|
/// Pure function. Same inputs always produce the same string. Tests verify the
|
|
/// edge cases (no tokens / one token / many / nested / unresolvable / bad
|
|
/// quality / null value).
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class MessageTemplate
|
|
{
|
|
private static readonly Regex TokenRegex = new(@"\{([^{}]+)\}",
|
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
|
|
/// <summary>
|
|
/// Resolve every <c>{path}</c> token in <paramref name="template"/> using
|
|
/// <paramref name="resolveTag"/>. Tokens whose returned <see cref="DataValueSnapshot"/>
|
|
/// has a non-Good <see cref="DataValueSnapshot.StatusCode"/> or a null
|
|
/// <see cref="DataValueSnapshot.Value"/> resolve to <c>{?}</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Quality bar is intentionally <em>stricter</em> than predicate evaluation:
|
|
/// only Good (StatusCode == 0) is substituted; Uncertain renders as
|
|
/// <c>{?}</c>. The predicate gate (<c>ScriptedAlarmEngine.AreInputsReady</c>)
|
|
/// accepts Uncertain because it still carries a value the predicate can
|
|
/// inspect, but the operator-facing message must make doubt explicit rather
|
|
/// than substituting a value an operator might act on. See the
|
|
/// "Input-quality policy" section in <c>docs/ScriptedAlarms.md</c>.
|
|
/// (Core.ScriptedAlarms-010)
|
|
/// </remarks>
|
|
/// <param name="template">The template string with {path} tokens.</param>
|
|
/// <param name="resolveTag">A function to resolve tag values by path.</param>
|
|
/// <returns>The resolved template string with tokens replaced or marked as unresolvable.</returns>
|
|
public static string Resolve(string template, Func<string, DataValueSnapshot?> resolveTag)
|
|
{
|
|
if (string.IsNullOrEmpty(template)) return template ?? string.Empty;
|
|
if (resolveTag is null) throw new ArgumentNullException(nameof(resolveTag));
|
|
|
|
return TokenRegex.Replace(template, match =>
|
|
{
|
|
var path = match.Groups[1].Value.Trim();
|
|
if (path.Length == 0) return "{?}";
|
|
var snap = resolveTag(path);
|
|
if (snap is null) return "{?}";
|
|
if (snap.StatusCode != 0u) return "{?}";
|
|
return snap.Value?.ToString() ?? "{?}";
|
|
});
|
|
}
|
|
|
|
/// <summary>Enumerate the token paths the template references. Used at publish time to validate references exist.</summary>
|
|
/// <param name="template">The template string to extract tokens from.</param>
|
|
/// <returns>A list of all token paths found in the template.</returns>
|
|
public static IReadOnlyList<string> ExtractTokenPaths(string? template)
|
|
{
|
|
if (string.IsNullOrEmpty(template)) return Array.Empty<string>();
|
|
var tokens = new List<string>();
|
|
foreach (Match m in TokenRegex.Matches(template))
|
|
{
|
|
var path = m.Groups[1].Value.Trim();
|
|
if (path.Length > 0) tokens.Add(path);
|
|
}
|
|
return tokens;
|
|
}
|
|
}
|