Browse Source

Fix bool and int serialisation issues

clanker 14 hours ago
parent
commit
fc38c4c957
4 changed files with 221 additions and 0 deletions
  1. 75 0
      src/lib/Json.vala
  2. 144 0
      src/tests/Integration/FromElement.vala
  3. 1 0
      src/tests/TestRunner.vala
  4. 1 0
      src/tests/meson.build

+ 75 - 0
src/lib/Json.vala

@@ -79,6 +79,81 @@ namespace InvercargillJson {
             }
 
             node = new Json.Node (Json.NodeType.VALUE);
+
+            // For ValueElement scalars, read the underlying GValue directly instead
+            // of going through element.as<T?>(). The generic extraction path is the
+            // authoritative source of the held type but is unsafe for 64-bit
+            // integers (it returns a corrupt value that crashes Json.Node.set_int)
+            // and the dispatch below has no branches for long/ulong (so they fall
+            // through to the string fallback and serialise as quoted JSON strings).
+            // Reading the raw GValue lets us dispatch on the fundamental type and
+            // use the matching g_value_get_* accessor for every numeric type.
+            // Fixes upstream issues I-1 (int64/uint64 segfault), I-2 (bool) and
+            // I-3 (numeric serialised as string).
+            if (element is Invercargill.ValueElement) {
+                var raw = ((Invercargill.ValueElement) element).get_value();
+                var value_type = raw.type();
+                if (value_type.is_a(Type.STRING)) {
+                    node.set_string(raw.get_string());
+                    return;
+                }
+                if (value_type.is_a(Type.BOOLEAN)) {
+                    node.set_boolean(raw.get_boolean());
+                    return;
+                }
+                if (value_type.is_a(Type.DOUBLE)) {
+                    node.set_double(raw.get_double());
+                    return;
+                }
+                if (value_type.is_a(Type.FLOAT)) {
+                    node.set_double((double)raw.get_float());
+                    return;
+                }
+                // Every integer fundamental collapses to JSON's single int node.
+                if (value_type.is_a(Type.INT64)) {
+                    node.set_int(raw.get_int64());
+                    return;
+                }
+                if (value_type.is_a(Type.UINT64)) {
+                    node.set_int((int64)raw.get_uint64());
+                    return;
+                }
+                if (value_type.is_a(Type.LONG)) {
+                    node.set_int((int64)raw.get_long());
+                    return;
+                }
+                if (value_type.is_a(Type.ULONG)) {
+                    node.set_int((int64)raw.get_ulong());
+                    return;
+                }
+                if (value_type.is_a(Type.INT)) {
+                    node.set_int((int64)raw.get_int());
+                    return;
+                }
+                if (value_type.is_a(Type.UINT)) {
+                    node.set_int((int64)raw.get_uint());
+                    return;
+                }
+                if (value_type.is_a(Type.CHAR)) {
+                    node.set_int((int64)raw.get_schar());
+                    return;
+                }
+                if (value_type.is_a(Type.UCHAR)) {
+                    node.set_int((int64)raw.get_uchar());
+                    return;
+                }
+                if (value_type.is_a(Type.ENUM)) {
+                    node.set_int((int64)raw.get_enum());
+                    return;
+                }
+                if (value_type.is_a(Type.FLAGS)) {
+                    node.set_int((int64)raw.get_flags());
+                    return;
+                }
+                // Non-fundamental held types (DateTime, BinaryData, objects, ...)
+                // fall through to the dispatch below.
+            }
+
             var type = element.type();
 
             // Third priority is conversion of known types

+ 144 - 0
src/tests/Integration/FromElement.vala

@@ -0,0 +1,144 @@
+using Invercargill;
+using Invercargill.DataStructures;
+using InvercargillJson;
+using Json;
+
+/**
+ * Regression tests for the ValueElement -> JsonElement.from_element scalar
+ * dispatch (upstream issues I-1, I-2, I-3).
+ *
+ * These mirror the downstream Statum path where a GObject property is wrapped
+ * in `new ValueElement(value)` and then serialised via
+ * `JsonElement.from_properties` / `from_element`.
+ */
+void from_element_scalar_tests() {
+
+    // --- I-1: int64 ValueElement must serialise as a JSON number, not segfault ---
+    Test.add_func("/invercargill/from_element/int64_is_number", () => {
+        if (Test.subprocess()) {
+            var v = Value(typeof(int64));
+            v.set_int64(99);
+            try {
+                var j = new JsonElement.from_element(new ValueElement(v));
+                assert_cmpstr(j.stringify(), CompareOperator.EQ, "99");
+            } catch (Error e) { assert_no_error(e); }
+            return;
+        }
+        Test.trap_subprocess(null, 0, 0);
+        Test.trap_assert_passed();
+    });
+
+    // --- I-1 (variant): uint64 ValueElement must serialise as a number, not segfault ---
+    Test.add_func("/invercargill/from_element/uint64_is_number", () => {
+        if (Test.subprocess()) {
+            var v = Value(typeof(uint64));
+            v.set_uint64(99);
+            try {
+                var j = new JsonElement.from_element(new ValueElement(v));
+                assert_cmpstr(j.stringify(), CompareOperator.EQ, "99");
+            } catch (Error e) { assert_no_error(e); }
+            return;
+        }
+        Test.trap_subprocess(null, 0, 0);
+        Test.trap_assert_passed();
+    });
+
+    // --- I-1: large int64 round-trips without precision loss (2^53 + 1) ---
+    Test.add_func("/invercargill/from_element/int64_large_precision", () => {
+        if (Test.subprocess()) {
+            int64 big = (int64)9007199254740993;
+            var v = Value(typeof(int64));
+            v.set_int64(big);
+            try {
+                var j = new JsonElement.from_element(new ValueElement(v));
+                assert_cmpstr(j.stringify(), CompareOperator.EQ, "9007199254740993");
+            } catch (Error e) { assert_no_error(e); }
+            return;
+        }
+        Test.trap_subprocess(null, 0, 0);
+        Test.trap_assert_passed();
+    });
+
+    // --- I-3: long (glong) ValueElement must be a JSON number, not a quoted string ---
+    Test.add_func("/invercargill/from_element/long_is_number", () => {
+        var v = Value(typeof(long));
+        v.set_long(7);
+        try {
+            var j = new JsonElement.from_element(new ValueElement(v));
+            // A number serialises as `7`; a string would serialise as `"7"`.
+            assert_cmpstr(j.stringify(), CompareOperator.EQ, "7");
+        } catch (Error e) { assert_no_error(e); }
+    });
+
+    // --- I-3: ulong (gulong) ValueElement must be a JSON number, not a quoted string ---
+    Test.add_func("/invercargill/from_element/ulong_is_number", () => {
+        var v = Value(typeof(ulong));
+        v.set_ulong(7);
+        try {
+            var j = new JsonElement.from_element(new ValueElement(v));
+            assert_cmpstr(j.stringify(), CompareOperator.EQ, "7");
+        } catch (Error e) { assert_no_error(e); }
+    });
+
+    // --- I-2: bool ValueElement must serialise as a JSON boolean ---
+    Test.add_func("/invercargill/from_element/bool_value", () => {
+        var v = Value(typeof(bool));
+        v.set_boolean(true);
+        try {
+            var j = new JsonElement.from_element(new ValueElement(v));
+            assert_cmpstr(j.stringify(), CompareOperator.EQ, "true");
+        } catch (Error e) { assert_no_error(e); }
+    });
+
+    // --- Comprehensive: the whole numeric family keeps its JSON wire type ---
+    Test.add_func("/invercargill/from_element/numeric_family", () => {
+        string check(string label, Value v, string expected) {
+            try {
+                var j = new JsonElement.from_element(new ValueElement(v));
+                assert_cmpstr(label, CompareOperator.EQ, label); // keep label on failure
+                return j.stringify();
+            } catch (Error e) {
+                assert_no_error(e);
+                return "";
+            }
+        }
+        var vi = Value(typeof(int)); vi.set_int(42);
+        assert_cmpstr(check("int", vi, ""), CompareOperator.EQ, "42");
+        var vu = Value(typeof(uint)); vu.set_uint(42u);
+        assert_cmpstr(check("uint", vu, ""), CompareOperator.EQ, "42");
+        var vc = Value(typeof(int8)); vc.set_schar(-3);
+        assert_cmpstr(check("int8", vc, ""), CompareOperator.EQ, "-3");
+        var vuc = Value(typeof(uint8)); vuc.set_uchar(3);
+        assert_cmpstr(check("uint8", vuc, ""), CompareOperator.EQ, "3");
+        var vd = Value(typeof(double)); vd.set_double(3.5);
+        assert_cmpstr(check("double", vd, ""), CompareOperator.EQ, "3.5");
+        var vf = Value(typeof(float)); vf.set_float(2.5f);
+        assert_cmpstr(check("float", vf, ""), CompareOperator.EQ, "2.5");
+    });
+
+    // --- Reported encryption path: from_properties with mixed scalars ---
+    Test.add_func("/invercargill/from_properties/mixed_scalars_roundtrip", () => {
+        if (Test.subprocess()) {
+            var dict = new PropertyDictionary();
+            var vi = Value(typeof(int64)); vi.set_int64(12345678901234);
+            dict.set("id", new ValueElement(vi));
+            var vb = Value(typeof(bool)); vb.set_boolean(true);
+            dict.set("admin", new ValueElement(vb));
+            var vl = Value(typeof(long)); vl.set_long(7);
+            dict.set("count", new ValueElement(vl));
+            try {
+                var j = new JsonElement.from_properties(dict);
+                var obj = new JsonElement.from_string(j.stringify()).assert_as<JsonObject>();
+                int64 id = obj.get_integer("id");
+                assert(id == (int64)12345678901234);
+                assert(obj.get_bool("admin") == true);
+                int64 count = obj.get_integer("count");
+                assert(count == (int64)7);
+            } catch (Error e) { assert_no_error(e); }
+            return;
+        }
+        Test.trap_subprocess(null, 0, 0);
+        Test.trap_assert_passed();
+    });
+
+}

+ 1 - 0
src/tests/TestRunner.vala

@@ -6,6 +6,7 @@ public static int main(string[] args) {
     promotion_tests();
     property_mapper_tests();
     json_tests();
+    from_element_scalar_tests();
     
     Test.run();
 

+ 1 - 0
src/tests/meson.build

@@ -3,5 +3,6 @@ sources = files('TestRunner.vala')
 sources += files('Integration/Promotion.vala')
 sources += files('Integration/PropertyMapper.vala')
 sources += files('Integration/Json.vala')
+sources += files('Integration/FromElement.vala')
 
 executable('invercargill-json-test-suite', sources, dependencies: dependencies, install: true)