using System.Text.Json; using System.Text.Json.Nodes; namespace ZB.MOM.WW.ScadaBridge.CLI.Commands; /// /// Table projection for deploy status (deferred-work register rows 37/87). /// /// /// The handler now returns a DeploymentQueryResult envelope — /// { "deployments": [ … ], "totalCount": N, "page": P, "pageSize": S } — where /// it used to return a bare row array. Handed to the generic renderer unprojected, /// an object takes the Property/Value branch and the whole deployments array /// lands in a single unreadable cell. This unwraps the envelope to the row array the /// renderer expects, exactly as does for /// audit-config query. /// /// /// /// The envelope's counts would be lost by a plain unwrap, and losing THOSE is the one /// thing this projection must not do: the page size is bounded server-side now, so a /// full page is no longer proof that there are no more rows. When the page is a strict /// subset a single trailing row carries a note cell saying how many of how many /// are shown and how to get the rest. When the page IS the whole result set the column /// is not emitted at all. /// /// /// /// JSON output is never touched — this runs only on the table path. /// /// internal static class DeploymentStatusTableProjection { /// /// Projects a DeploymentQueryResult envelope (or a bare row array) to the /// 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 apply. /// /// The raw success JSON body from the management API. /// JSON suitable for table rendering. internal static string ProjectRows(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 command still renders usefully against // an older server that returns the pre-envelope shape. if (root.ValueKind == JsonValueKind.Array) return BuildRows(root, totalCount: null).ToJsonString(); if (root.ValueKind == JsonValueKind.Object && TryGetPropertyCI(root, "deployments", out var rows) && rows.ValueKind == JsonValueKind.Array) { int? total = TryGetPropertyCI(root, "totalCount", out var tc) && tc.ValueKind == JsonValueKind.Number && tc.TryGetInt32(out var n) ? n : null; return BuildRows(rows, total).ToJsonString(); } // Some other object (e.g. an error envelope) — leave it to the renderer. return json; } } private static JsonArray BuildRows(JsonElement rows, int? totalCount) { var arr = new JsonArray(); var shown = 0; foreach (var item in rows.EnumerateArray()) { arr.Add(item.ValueKind == JsonValueKind.Object ? JsonNode.Parse(item.GetRawText())! : JsonValue.Create(item.ToString())!); 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; } 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; } }