using System.Text.Json;
using System.Text.Json.Nodes;
namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;
///
/// Compact table projection for audit-config query (deferred-work register row 57).
///
///
/// The handler returns an AuditLogQueryResult — { "entries": [ … ],
/// "totalCount": N }. Handed to the generic renderer unprojected, that object takes
/// the Property/Value branch and the whole entries array lands in a single cell as
/// raw JSON, so --format table was unreadable. This unwraps the envelope to the
/// row array the renderer expects and drops afterStateJson, the per-entry snapshot
/// of the entire changed entity — the same "one fat field makes the table useless" problem
/// solves by dropping child collections.
///
///
///
/// totalCount would be lost by a plain unwrap, so when the page is a strict subset
/// of the matches a single trailing row carries a note cell saying how many of how
/// many are shown. Every real entry leaves that cell empty, and when the page IS the whole
/// result set the column is not emitted at all — so the common small-query table stays
/// exactly as narrow as its data.
///
///
///
/// JSON output is never touched (this runs only on the table path), and the command's
/// --detail flag skips the projection to render the raw payload as-is — that is
/// the way to see afterStateJson in table form.
///
///
internal static class AuditConfigTableProjection
{
///
/// Projects an AuditLogQueryResult envelope (or a bare entry array) to the
/// compact row array the table renderer consumes. Returns the input unchanged when it
/// is not JSON or not one of those shapes, so the renderer's own fallbacks still apply.
///
/// The raw success JSON body from the management API.
/// Compact JSON suitable for table rendering.
internal static string ProjectEntries(string json)
{
JsonDocument doc;
try
{
doc = JsonDocument.Parse(json);
}
catch (JsonException)
{
// Not JSON (e.g. a proxy error page) — let the renderer print it verbatim.
return json;
}
using (doc)
{
var root = doc.RootElement;
// A bare array is accepted so the projection still works if the payload is
// ever unwrapped upstream, and so the deprecated `audit-log` alias against an
// older server does not render worse than it does today.
if (root.ValueKind == JsonValueKind.Array)
{
return BuildRows(root, totalCount: null).ToJsonString();
}
if (root.ValueKind == JsonValueKind.Object
&& TryGetPropertyCI(root, "entries", out var entries)
&& entries.ValueKind == JsonValueKind.Array)
{
int? total = TryGetPropertyCI(root, "totalCount", out var tc)
&& tc.ValueKind == JsonValueKind.Number
&& tc.TryGetInt32(out var n)
? n
: null;
return BuildRows(entries, total).ToJsonString();
}
// Some other object (e.g. an error envelope) — leave it to the renderer.
return json;
}
}
private static JsonArray BuildRows(JsonElement entries, int? totalCount)
{
var arr = new JsonArray();
var shown = 0;
foreach (var item in entries.EnumerateArray())
{
arr.Add(ProjectElement(item));
shown++;
}
// Only when the page is a strict subset — otherwise the count is just the row
// count the operator can already see, and the extra column would be noise.
if (totalCount is int total && total > shown)
{
arr.Add(new JsonObject
{
["note"] = JsonValue.Create(
$"showing {shown} of {total} — use --page / --page-size for the rest"),
});
}
return arr;
}
/// Projects a single audit entry to its compact row node.
private static JsonNode ProjectElement(JsonElement element)
{
if (element.ValueKind != JsonValueKind.Object)
return JsonValue.Create(element.ToString())!;
// JsonObject preserves insertion order, which fixes the table's column order.
// afterStateJson (the full post-change entity snapshot) and bundleImportId are
// deliberately omitted — `--detail`, or --format json, for those.
return new JsonObject
{
["id"] = Int(element, "id"),
["timestamp"] = Str(element, "timestamp"),
["user"] = Str(element, "user"),
["action"] = Str(element, "action"),
["entityType"] = Str(element, "entityType"),
["entityName"] = Str(element, "entityName"),
["entityId"] = Str(element, "entityId"),
};
}
private static bool TryGetPropertyCI(JsonElement obj, string name, out JsonElement value)
{
foreach (var prop in obj.EnumerateObject())
{
if (string.Equals(prop.Name, name, StringComparison.OrdinalIgnoreCase))
{
value = prop.Value;
return true;
}
}
value = default;
return false;
}
private static JsonNode? Str(JsonElement obj, string name)
=> TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.String
? JsonValue.Create(v.GetString())
: null;
private static JsonNode? Int(JsonElement obj, string name)
=> TryGetPropertyCI(obj, name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n)
? JsonValue.Create(n)
: null;
}