refactor(audit): consolidate query-param parsers; widen CLI export to multi-value

This commit is contained in:
Joseph Doherty
2026-05-21 05:37:06 -04:00
parent 2a76be1f94
commit f64a7aed02
9 changed files with 350 additions and 177 deletions

View File

@@ -0,0 +1,79 @@
namespace ScadaLink.Commons.Types.Audit;
/// <summary>
/// Shared lax parsers for the multi-value Audit Log query parameters
/// (<c>channel</c>/<c>kind</c>/<c>status</c>/<c>site</c>). The Audit Log filter
/// wire-contract is consumed by three surfaces that MUST stay in lockstep:
/// <list type="bullet">
/// <item>the ManagementService <c>/api/audit/query</c> + <c>/api/audit/export</c>
/// endpoints,</item>
/// <item>the CentralUI <c>/api/centralui/audit/export</c> endpoint, and</item>
/// <item>the CentralUI <c>AuditLogPage</c> query-string drill-in parser.</item>
/// </list>
///
/// <para>
/// Each caller extracts the raw repeated values for a single parameter from its
/// own request type (ASP.NET <c>IQueryCollection</c>, a
/// <c>Dictionary&lt;string, StringValues&gt;</c> from <c>QueryHelpers.ParseQuery</c>,
/// etc.) and passes them here as a plain <see cref="IEnumerable{T}"/> of strings —
/// so this helper carries NO ASP.NET / <c>Microsoft.Extensions.Primitives</c>
/// dependency and can live in <c>ScadaLink.Commons</c>.
/// </para>
///
/// <para>
/// <b>Lax-parse contract.</b> Every value of a repeated parameter is parsed
/// independently; an unparseable or blank element is silently dropped (NO 400)
/// rather than failing the whole set. An empty result collapses to <c>null</c> so
/// the corresponding filter dimension stays unconstrained.
/// </para>
/// </summary>
public static class AuditQueryParamParsers
{
/// <summary>
/// Parses each raw value as <typeparamref name="TEnum"/> (case-insensitive),
/// dropping unparseable values silently. Returns <c>null</c> when
/// <paramref name="rawValues"/> is <c>null</c>, empty, or yields no parseable
/// value — so the filter dimension stays unconstrained.
/// </summary>
public static IReadOnlyList<TEnum>? ParseEnumList<TEnum>(IEnumerable<string?>? rawValues)
where TEnum : struct, Enum
{
if (rawValues is null)
{
return null;
}
var parsed = new List<TEnum>();
foreach (var raw in rawValues)
{
if (Enum.TryParse<TEnum>(raw, ignoreCase: true, out var value))
{
parsed.Add(value);
}
}
return parsed.Count > 0 ? parsed : null;
}
/// <summary>
/// Trims each raw value and drops blank entries. Returns <c>null</c> when
/// <paramref name="rawValues"/> is <c>null</c>, empty, or every value was
/// blank.
/// </summary>
public static IReadOnlyList<string>? ParseStringList(IEnumerable<string?>? rawValues)
{
if (rawValues is null)
{
return null;
}
var parsed = new List<string>();
foreach (var raw in rawValues)
{
if (!string.IsNullOrWhiteSpace(raw))
{
parsed.Add(raw.Trim());
}
}
return parsed.Count > 0 ? parsed : null;
}
}