Jelajahi Sumber

fix(json): tighten parse_to_nlohmann error path + test contract

Code review of f059034 flagged two boundary issues since this helper
will be called from 17 places in tasks T3-T7:

1. The "empty input" early-return produced a bespoke error message that
   broke the parse_error shape consistency 17 downstream callsites would
   see. Drop the length==0 branch; let yyjson emit its positional
   "empty content" error wrapped in parse_error like every other
   malformed-input case. Null guard retained (yyjson would segfault).

2. test_invalid_json_throws caught std::exception, allowing a future
   refactor to silently change the throw type and break every
   catch (nlohmann::json::parse_error&) site downstream without test
   failure. Tighten to catch the specific type. Add
   test_empty_string_throws_parse_error to lock in the post-fix
   empty-input behaviour.
fszontagh 2 bulan lalu
induk
melakukan
bae1b04248
2 mengubah file dengan 28 tambahan dan 4 penghapusan
  1. 6 2
      service/src/json_parse.cpp
  2. 22 2
      tests/test_json_parse.cpp

+ 6 - 2
service/src/json_parse.cpp

@@ -57,9 +57,13 @@ nlohmann::json convert(yyjson_val* v) {
 }  // namespace
 
 nlohmann::json parse_to_nlohmann(const char* data, size_t length) {
-    if (!data || length == 0) {
+    // Null guard only. Empty buffers (length == 0) deliberately fall through
+    // to yyjson, which returns a positional "empty content" error — keeping
+    // the parse_error message shape consistent with non-empty malformed
+    // inputs across all 17 callsites.
+    if (!data) {
         throw nlohmann::json::parse_error::create(
-            101, 0, "empty input", nullptr);
+            101, 0, "null input buffer", nullptr);
     }
     yyjson_read_err err{};
     yyjson_doc* doc = yyjson_read_opts(

+ 22 - 2
tests/test_json_parse.cpp

@@ -63,13 +63,32 @@ void test_string_view_overload() {
 }
 
 void test_invalid_json_throws() {
+    // Contract: parse_to_nlohmann throws nlohmann::json::parse_error so the
+    // 17 downstream callsites that catch (nlohmann::json::parse_error&) keep
+    // working unchanged. Catching the specific type guards against a future
+    // refactor silently changing the throw type.
     bool threw = false;
     try {
         (void)parse_to_nlohmann("{not json");
-    } catch (const std::exception&) {
+    } catch (const nlohmann::json::parse_error&) {
         threw = true;
     }
-    check(threw, "invalid json throws");
+    check(threw, "invalid json throws parse_error");
+}
+
+void test_empty_string_throws_parse_error() {
+    // After the v1.10 fix, empty input is no longer pre-rejected with a
+    // bespoke "empty input" message — it falls through to yyjson, which
+    // emits a positional "unexpected end of data" error wrapped in
+    // parse_error. Verifies the message-path consistency the reviewer asked
+    // for so that operators see the same error shape for empty vs. malformed.
+    bool threw = false;
+    try {
+        (void)parse_to_nlohmann("");
+    } catch (const nlohmann::json::parse_error&) {
+        threw = true;
+    }
+    check(threw, "empty string throws parse_error");
 }
 
 void test_corpus_equivalence() {
@@ -98,6 +117,7 @@ int main() {
     test_large_int();
     test_string_view_overload();
     test_invalid_json_throws();
+    test_empty_string_throws_parse_error();
     test_corpus_equivalence();
     std::cout << "test_json_parse: all passed\n";
     return 0;