| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- 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<string>();
- 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<TaskView>(rows);`
- */
- public static JsonElement list<T>(Lot<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);
- }
- /** Same as {@link list} but accepts a Vala array. */
- public static JsonElement list_array<T>(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<string>(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<T>(Lot<T> 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);
- }
- }
- }
|