145 lines
4.8 KiB
C#
145 lines
4.8 KiB
C#
using System.Globalization;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace w4c_workflows.Services.Nodes.Executors;
|
|
|
|
/// <summary>
|
|
/// Typed readers over resolved node parameters. YAML scalars arrive as int,
|
|
/// long, decimal, double, bool or string, so the readers normalise the shapes
|
|
/// and apply a fallback instead of throwing. Shared so every executor reads a
|
|
/// bool/number/array the same way.
|
|
/// </summary>
|
|
internal static class NodeValueAccess
|
|
{
|
|
public static string? ReadString(JsonObject? parameters, string name)
|
|
=> parameters?[name] is JsonValue value && value.TryGetValue<string>(out var text) ? text : null;
|
|
|
|
public static bool ReadBool(JsonObject? parameters, string name, bool defaultValue = false)
|
|
=> parameters?[name] is JsonValue value && value.TryGetValue<bool>(out var flag) ? flag : defaultValue;
|
|
|
|
public static int ReadInt(JsonObject? parameters, string name, int defaultValue)
|
|
=> TryReadNumber(parameters?[name], out var number) ? (int)number : defaultValue;
|
|
|
|
/// <summary>Reads any JSON number shape (int/long/decimal/double) or a numeric string.</summary>
|
|
public static bool TryReadNumber(JsonNode? node, out double value)
|
|
{
|
|
value = 0;
|
|
if (node is not JsonValue jsonValue)
|
|
return false;
|
|
|
|
if (jsonValue.TryGetValue<double>(out var doubleValue))
|
|
{
|
|
value = doubleValue;
|
|
return true;
|
|
}
|
|
if (jsonValue.TryGetValue<decimal>(out var decimalValue))
|
|
{
|
|
value = (double)decimalValue;
|
|
return true;
|
|
}
|
|
if (jsonValue.TryGetValue<long>(out var longValue))
|
|
{
|
|
value = longValue;
|
|
return true;
|
|
}
|
|
if (jsonValue.TryGetValue<int>(out var intValue))
|
|
{
|
|
value = intValue;
|
|
return true;
|
|
}
|
|
|
|
return jsonValue.TryGetValue<string>(out var text)
|
|
&& double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
|
|
}
|
|
|
|
/// <summary>Decimal-precision numeric read (numbers, numeric strings).</summary>
|
|
public static bool TryReadDecimal(JsonNode? node, out decimal value)
|
|
{
|
|
value = 0;
|
|
if (node is not JsonValue jsonValue)
|
|
return false;
|
|
|
|
if (jsonValue.TryGetValue<decimal>(out var decimalValue))
|
|
{
|
|
value = decimalValue;
|
|
return true;
|
|
}
|
|
if (jsonValue.TryGetValue<double>(out var doubleValue))
|
|
{
|
|
value = (decimal)doubleValue;
|
|
return true;
|
|
}
|
|
if (jsonValue.TryGetValue<long>(out var longValue))
|
|
{
|
|
value = longValue;
|
|
return true;
|
|
}
|
|
|
|
return jsonValue.TryGetValue<string>(out var text)
|
|
&& decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
|
}
|
|
|
|
/// <summary>Boolean read (JSON booleans only).</summary>
|
|
public static bool TryReadBool(JsonNode? node, out bool flag)
|
|
{
|
|
flag = false;
|
|
return node is JsonValue value && value.TryGetValue<bool>(out flag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders a scalar node as text: strings verbatim, everything else as JSON
|
|
/// (objects/arrays), so callers can compare and interpolate uniformly.
|
|
/// </summary>
|
|
public static string ScalarText(JsonNode? node)
|
|
=> node switch
|
|
{
|
|
null => string.Empty,
|
|
JsonValue value when value.TryGetValue<string>(out var text) => text,
|
|
_ => node.ToJsonString(),
|
|
};
|
|
|
|
/// <summary>Shortens text for an error message, keeping the tail marker.</summary>
|
|
public static string Truncate(string text, int max = 512)
|
|
=> text.Length <= max ? text : text[..max] + "…";
|
|
|
|
/// <summary>Parses a JSON body, returning null for empty/invalid input instead of throwing.</summary>
|
|
public static JsonNode? TryParseJson(string body)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(body))
|
|
return null;
|
|
|
|
try
|
|
{
|
|
return JsonNode.Parse(body);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders a fixed-collection parameter as a list of objects. The
|
|
/// interpolator yields an array for the normal case, but a single entry may
|
|
/// arrive as a bare object; both are accepted.
|
|
/// </summary>
|
|
public static IReadOnlyList<JsonObject> ReadEntries(JsonObject parameters, string name)
|
|
{
|
|
var entries = new List<JsonObject>();
|
|
switch (parameters[name])
|
|
{
|
|
case JsonArray array:
|
|
foreach (var element in array)
|
|
{
|
|
if (element is JsonObject entry)
|
|
entries.Add(entry);
|
|
}
|
|
break;
|
|
case JsonObject single:
|
|
entries.Add(single);
|
|
break;
|
|
}
|
|
return entries;
|
|
}
|
|
}
|