fix(commons): resolve Commons-005..007,009..012 — OPC UA parse status, TryConvert correctness, Result null guard, invariant formatting, doc refresh

This commit is contained in:
Joseph Doherty
2026-05-16 22:04:21 -04:00
parent 746ab90444
commit c07f524ca4
12 changed files with 602 additions and 99 deletions

View File

@@ -55,8 +55,19 @@ public class DynamicJsonElement : DynamicObject
public override bool TryConvert(ConvertBinder binder, out object? result)
{
// Conversion to object (or dynamic): never null out a present value. Return the
// unwrapped value for scalars, this wrapper for objects/arrays, and null only
// when the element is genuinely JSON null.
if (binder.Type == typeof(object))
{
result = _element.ValueKind == JsonValueKind.Null ? null : Wrap(_element);
return true;
}
result = ConvertTo(binder.Type);
return result != null || binder.Type == typeof(object);
// A non-object target with a null result means ConvertTo could not handle the
// element/type pair — report failure so the binder surfaces a binding error.
return result != null;
}
public override string ToString()

View File

@@ -14,6 +14,9 @@ public sealed class Result<T>
private Result(string error)
{
// A failed Result must always carry a usable message — Result is the
// system-wide error-handling type, and consumers log/display Error directly.
ArgumentException.ThrowIfNullOrWhiteSpace(error);
_value = default;
_error = error;
IsSuccess = false;
@@ -33,6 +36,11 @@ public sealed class Result<T>
public static Result<T> Success(T value) => new(value);
/// <summary>
/// Creates a failed result carrying the given error message.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="error"/> is empty or whitespace.</exception>
public static Result<T> Failure(string error) => new(error);
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure) =>

View File

@@ -1,4 +1,5 @@
using System.Collections;
using System.Globalization;
namespace ScadaLink.Commons.Types;
@@ -9,21 +10,36 @@ namespace ScadaLink.Commons.Types;
public static class ValueFormatter
{
/// <summary>
/// Formats a value for display as a string. Returns the value's natural
/// string representation for scalars, and comma-separated elements for
/// array/collection types.
/// Formats a value as a string. Returns the value's string representation for
/// scalars and comma-separated elements for array/collection types.
/// </summary>
/// <remarks>
/// Formatting is <see cref="CultureInfo.InvariantCulture">culture-invariant</see>:
/// numbers and <see cref="DateTime"/> values render the same regardless of the
/// server/thread locale. This is required because the formatter feeds non-UI
/// contexts (gRPC stream events, logs, diff display) where locale-dependent
/// output (decimal separators, date order) would be inconsistent.
/// </remarks>
public static string FormatDisplayValue(object? value)
{
if (value is null) return "";
if (value is string s) return s;
if (value is IFormattable) return value.ToString() ?? "";
if (value is IFormattable formattable)
return formattable.ToString(null, CultureInfo.InvariantCulture) ?? "";
if (value is IEnumerable enumerable)
{
return string.Join(",", enumerable.Cast<object?>().Select(e => e?.ToString() ?? ""));
return string.Join(",", enumerable.Cast<object?>().Select(FormatElement));
}
return value.ToString() ?? "";
}
private static string FormatElement(object? element) => element switch
{
null => "",
string str => str,
IFormattable f => f.ToString(null, CultureInfo.InvariantCulture) ?? "",
_ => element.ToString() ?? ""
};
}