feat(cli): typed setpoint flags for template alarm add (serializes trigger-config JSON)

This commit is contained in:
Joseph Doherty
2026-06-07 10:02:51 -04:00
parent 9d7e69056a
commit bbc3804d07
2 changed files with 122 additions and 2 deletions
@@ -0,0 +1,80 @@
using System.Text;
using System.Text.Json;
namespace ZB.MOM.WW.ScadaBridge.CLI;
/// <summary>
/// Serializes typed alarm-setpoint CLI flags into the trigger-config JSON the
/// server expects. Key names MUST stay in lockstep with the canonical codec at
/// src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/AlarmTriggerConfigCodec.cs
/// (that codec is internal to CentralUI, so this is a deliberate CLI-side mirror;
/// the round-trip test verifies the JSON against the live server — the real contract).
/// </summary>
internal static class AlarmTriggerConfigJson
{
/// <summary>
/// Builds the trigger-config JSON for <paramref name="triggerType"/> from the typed
/// flags, or returns null when none are supplied (so the alarm is created without a
/// trigger config). Unknown/blank trigger types yield null.
/// </summary>
internal static string? Build(
string triggerType, string? attribute,
string? matchValue, bool notEquals,
double? min, double? max,
double? thresholdPerSecond, double? windowSeconds, string? direction,
double? loLo, double? lo, double? hi, double? hiHi,
string? expression)
{
var type = triggerType?.Trim();
var anyTyped = attribute is not null || matchValue is not null || notEquals
|| min.HasValue || max.HasValue || thresholdPerSecond.HasValue || windowSeconds.HasValue
|| direction is not null || loLo.HasValue || lo.HasValue || hi.HasValue || hiHi.HasValue
|| expression is not null;
if (!anyTyped) return null;
using var stream = new MemoryStream();
using (var w = new Utf8JsonWriter(stream))
{
w.WriteStartObject();
if (!string.Equals(type, "Expression", StringComparison.OrdinalIgnoreCase))
w.WriteString("attributeName", attribute ?? "");
switch (type?.ToLowerInvariant())
{
case "valuematch":
var mv = matchValue ?? "";
if (notEquals) mv = "!=" + mv;
w.WriteString("matchValue", mv);
break;
case "rangeviolation":
if (min.HasValue) w.WriteNumber("min", min.Value);
if (max.HasValue) w.WriteNumber("max", max.Value);
break;
case "rateofchange":
if (thresholdPerSecond.HasValue) w.WriteNumber("thresholdPerSecond", thresholdPerSecond.Value);
if (windowSeconds.HasValue) w.WriteNumber("windowSeconds", windowSeconds.Value);
w.WriteString("direction", NormalizeDirection(direction));
break;
case "hilo":
if (loLo.HasValue) w.WriteNumber("loLo", loLo.Value);
if (lo.HasValue) w.WriteNumber("lo", lo.Value);
if (hi.HasValue) w.WriteNumber("hi", hi.Value);
if (hiHi.HasValue) w.WriteNumber("hiHi", hiHi.Value);
break;
case "expression":
w.WriteString("expression", expression ?? "");
break;
}
w.WriteEndObject();
}
return Encoding.UTF8.GetString(stream.ToArray());
}
// Mirrors AlarmTriggerConfigCodec.NormalizeDirection.
private static string NormalizeDirection(string? raw) => raw?.ToLowerInvariant() switch
{
"rising" or "up" or "positive" => "rising",
"falling" or "down" or "negative" => "falling",
_ => "either",
};
}