using Invercargill; using Invercargill.DataStructures; using InvercargillJson; using Statum; namespace ClankerSaddle { /** * Helpers for reading the JSON objects that travel the wire — * pushed results/chunks entries, block payloads and request * bodies. One implementation of each member shape (string, * nullable string, string array — native or JSON-encoded — and * ISO-8601 stamps), shared by SaddleService, SaddleApi and * TaskService instead of three private copies with drifting * edge cases. */ public class EntryJson : Object { /** A string member: "" when absent or not a string. */ public static string string_member(Json.Object obj, string name) { return obj.has_member(name) && obj.get_member(name).get_string() != null ? obj.get_string_member(name) : ""; } /** A nullable string member: null when absent or empty. */ public static string? optional_string_member(Json.Object obj, string name) { string value = string_member(obj, name); return value.length > 0 ? value : null; } /** A string member, or `fallback` when absent or not a string. */ public static string get_string_or(Json.Object obj, string name, string? fallback) { if (obj.has_member(name) && obj.get_member(name).get_string() != null) { return obj.get_string_member(name); } return fallback; } /** * An ISO-8601 string member as a DateTime (null when absent or * not a string; a bare local time reads as UTC, the daemon's * clock domain). */ public static DateTime? parse_iso(Json.Object obj, string name) { if (!obj.has_member(name)) { return null; } var value = obj.get_member(name); if (value.get_node_type() != Json.NodeType.VALUE || value.get_string() == null) { return null; } return new DateTime.from_iso8601(value.get_string(), new TimeZone.utc()); } /** * A string-array member: native JSON arrays are read * element-wise; a JSON-encoded string is parsed; anything else * is empty. `what` names the member in the malformed-payload * warning. */ public static string[] string_array_member(Json.Object obj, string name, string what = "options array") { if (!obj.has_member(name)) { return new string[0]; } var member = obj.get_member(name); if (member.get_node_type() == Json.NodeType.ARRAY) { return elements_of(member.get_array()); } string raw = string_member(obj, name); if (raw.length == 0) { return new string[0]; } try { var parser = new Json.Parser(); parser.load_from_data(raw); var root = parser.get_root(); if (root != null && root.get_node_type() == Json.NodeType.ARRAY) { return elements_of(root.get_array()); } } catch (Error e) { warning("[ClankerSaddle] Ignoring malformed %s: %s", what, e.message); } return new string[0]; } /** * A stored JSON array-of-strings text (a block's options * column) back into a Vala array; null, empty, non-array or * malformed input is empty. */ public static string[] parse_string_array(string? raw) { if (raw == null || ((!)raw).length == 0) { return new string[0]; } try { var parser = new Json.Parser(); parser.load_from_data((!)raw); var root = parser.get_root(); if (root == null || root.get_node_type() != Json.NodeType.ARRAY) { return new string[0]; } return elements_of(root.get_array()); } catch (Error e) { return new string[0]; } } /** A JSON array's string elements (non-strings are skipped). */ private static string[] elements_of(Json.Array array) { var values = new Vector(); foreach (var element in array.get_elements()) { if (element.get_node_type() == Json.NodeType.VALUE && element.get_string() != null) { values.add(element.get_string()); } } return values.to_array(); } } /** * Helpers for shaping Statum slot state. Because Statum's GObjectMapping * cannot serialise array/list fields, lists are built as a JsonArray and set * on a PropertyDictionary under a key, where stm-for-{x}-in can iterate them. */ public class StateJson : Object { /** * Builds a JSON array element from a lot of GObject row models, mapping * each row via GObjectMapping. Use the result as a slot public-data value: * `dict["tasks"] = StateJson.list(rows);` */ public static JsonElement list(Lot items) throws GLib.Error { var arr = new JsonArray(); foreach (var item in items) { var props = GObjectMapping.to_properties((Object) item); arr.add(new JsonElement.from_properties(props)); } return new JsonElement.from_elements(arr); } /** Same as {@link list} but accepts a Vala array. */ public static JsonElement list_array(T[] items) throws GLib.Error { var arr = new JsonArray(); foreach (var item in items) { var props = GObjectMapping.to_properties((Object) item); arr.add(new JsonElement.from_properties(props)); } return new JsonElement.from_elements(arr); } /** * A single row model as a slot public-data value (the object * twin of {@link list}): `dict[\"current\"] = StateJson.element(row);` * — null-safe, a null row maps to a JSON null. */ public static JsonElement? element(Object? item) throws GLib.Error { if (item == null) { return null; } var props = GObjectMapping.to_properties((!)item); return new JsonElement.from_properties(props); } /** * A JSON array of plain strings (e.g. a block's multiple-choice * options). */ public static JsonElement strings(string[] values) throws GLib.Error { var arr = new JsonArray(); foreach (var value in values) { arr.add(new JsonElement.from_element(new NativeElement(value))); } return new JsonElement.from_elements(arr); } /** * Per-row enrichment hook for {@link list_rich}: after each item * is mapped, the enricher may add array/complex values the plain * GObject mapping cannot serialise (e.g. a block's option list). */ public delegate void PropertyEnricher(Object item, PropertyDictionary props) throws GLib.Error; /** * Like {@link list}, but every row's property dictionary passes * through `enrich` before serialisation so callers can attach * fields GObjectMapping drops (arrays, nested objects). */ public static JsonElement list_rich(Lot items, owned PropertyEnricher enrich) throws GLib.Error { var arr = new JsonArray(); foreach (var item in items) { var props = (PropertyDictionary) GObjectMapping.to_properties((Object) item); enrich((Object) item, props); arr.add(new JsonElement.from_properties(props)); } return new JsonElement.from_elements(arr); } } }