Yaml encoder/decoder for OCaml jsont codecs

fixes

+130 -19
+5 -3
lib/yamlt.ml
··· 154 154 | None -> err_type_mismatch d ev.span t ~fnd:("scalar " ^ value)) 155 155 | String map -> 156 156 (* Don't decode null values as strings - they should fail so outer combinators 157 - like 'option' or 'any' can handle them properly *) 158 - if is_null_scalar value then 157 + like 'option' or 'any' can handle them properly. 158 + BUT: quoted strings should always be treated as strings, even if they 159 + look like null (e.g., "" or "null") *) 160 + if style = `Plain && is_null_scalar value then 159 161 err_type_mismatch d ev.span t ~fnd:"null" 160 162 else 161 - (* Strings accept any non-null scalar value *) 163 + (* Strings accept quoted scalars or non-null plain scalars *) 162 164 map.dec meta value 163 165 | Map m -> 164 166 (* Handle Map combinators (e.g., from Jsont.option) *)
+8
tests/bin/dune
··· 49 49 (executable 50 50 (name test_some_vs_option) 51 51 (libraries yamlt jsont jsont.bytesrw bytesrw)) 52 + 53 + (executable 54 + (name test_comprehensive) 55 + (libraries yamlt jsont jsont.bytesrw bytesrw)) 56 + 57 + (executable 58 + (name test_flow_newline) 59 + (libraries yamlt jsont jsont.bytesrw bytesrw))
+88
tests/bin/test_comprehensive.ml
··· 1 + let () = 2 + (* Test 1: Null handling with option types *) 3 + Printf.printf "=== NULL HANDLING ===\n"; 4 + let opt_codec = 5 + Jsont.Object.map ~kind:"Test" (fun v -> v) 6 + |> Jsont.Object.mem "value" (Jsont.option Jsont.string) ~enc:(fun v -> v) 7 + |> Jsont.Object.finish 8 + in 9 + 10 + (match Yamlt.decode_string opt_codec "value: null" with 11 + | Ok None -> Printf.printf "✓ Plain 'null' with option codec: None\n" 12 + | _ -> Printf.printf "✗ FAIL\n"); 13 + 14 + (match Yamlt.decode_string opt_codec "value: hello" with 15 + | Ok (Some "hello") -> Printf.printf "✓ Plain 'hello' with option codec: Some(hello)\n" 16 + | _ -> Printf.printf "✗ FAIL\n"); 17 + 18 + let string_codec = 19 + Jsont.Object.map ~kind:"Test" (fun v -> v) 20 + |> Jsont.Object.mem "value" Jsont.string ~enc:(fun v -> v) 21 + |> Jsont.Object.finish 22 + in 23 + 24 + (match Yamlt.decode_string string_codec "value: null" with 25 + | Error _ -> Printf.printf "✓ Plain 'null' with string codec: ERROR (expected)\n" 26 + | _ -> Printf.printf "✗ FAIL\n"); 27 + 28 + (match Yamlt.decode_string string_codec "value: \"\"" with 29 + | Ok "" -> Printf.printf "✓ Quoted empty string: \"\"\n" 30 + | _ -> Printf.printf "✗ FAIL\n"); 31 + 32 + (match Yamlt.decode_string string_codec "value: \"null\"" with 33 + | Ok "null" -> Printf.printf "✓ Quoted 'null': \"null\"\n" 34 + | _ -> Printf.printf "✗ FAIL\n"); 35 + 36 + (* Test 2: Number formats *) 37 + Printf.printf "\n=== NUMBER FORMATS ===\n"; 38 + let num_codec = 39 + Jsont.Object.map ~kind:"Test" (fun v -> v) 40 + |> Jsont.Object.mem "value" Jsont.number ~enc:(fun v -> v) 41 + |> Jsont.Object.finish 42 + in 43 + 44 + (match Yamlt.decode_string num_codec "value: 0xFF" with 45 + | Ok 255. -> Printf.printf "✓ Hex 0xFF: 255\n" 46 + | _ -> Printf.printf "✗ FAIL\n"); 47 + 48 + (match Yamlt.decode_string num_codec "value: 0o77" with 49 + | Ok 63. -> Printf.printf "✓ Octal 0o77: 63\n" 50 + | _ -> Printf.printf "✗ FAIL\n"); 51 + 52 + (match Yamlt.decode_string num_codec "value: 0b1010" with 53 + | Ok 10. -> Printf.printf "✓ Binary 0b1010: 10\n" 54 + | _ -> Printf.printf "✗ FAIL\n"); 55 + 56 + (* Test 3: Optional arrays *) 57 + Printf.printf "\n=== OPTIONAL ARRAYS ===\n"; 58 + let opt_array_codec = 59 + Jsont.Object.map ~kind:"Test" (fun v -> v) 60 + |> Jsont.Object.opt_mem "values" (Jsont.array Jsont.string) ~enc:(fun v -> v) 61 + |> Jsont.Object.finish 62 + in 63 + 64 + (match Yamlt.decode_string opt_array_codec "values: [a, b, c]" with 65 + | Ok (Some arr) when Array.length arr = 3 -> 66 + Printf.printf "✓ Optional array [a, b, c]: Some([3 items])\n" 67 + | _ -> Printf.printf "✗ FAIL\n"); 68 + 69 + (match Yamlt.decode_string opt_array_codec "{}" with 70 + | Ok None -> Printf.printf "✓ Missing optional array: None\n" 71 + | _ -> Printf.printf "✗ FAIL\n"); 72 + 73 + (* Test 4: Flow encoding *) 74 + Printf.printf "\n=== FLOW ENCODING ===\n"; 75 + let encode_codec = 76 + Jsont.Object.map ~kind:"Test" (fun name values -> (name, values)) 77 + |> Jsont.Object.mem "name" Jsont.string ~enc:fst 78 + |> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:snd 79 + |> Jsont.Object.finish 80 + in 81 + 82 + (match Yamlt.encode_string ~format:Flow encode_codec ("test", [|1.; 2.; 3.|]) with 83 + | Ok yaml_flow when String.equal yaml_flow "{name: test, values: [1.0, 2.0, 3.0]}\n" -> 84 + Printf.printf "✓ Flow encoding with comma separator\n" 85 + | Ok yaml_flow -> 86 + Printf.printf "✗ FAIL: %S\n" yaml_flow 87 + | Error e -> 88 + Printf.printf "✗ ERROR: %s\n" e)
+14
tests/bin/test_flow_newline.ml
··· 1 + let () = 2 + let encode_codec = 3 + Jsont.Object.map ~kind:"Test" (fun name values -> (name, values)) 4 + |> Jsont.Object.mem "name" Jsont.string ~enc:fst 5 + |> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:snd 6 + |> Jsont.Object.finish 7 + in 8 + 9 + match Yamlt.encode_string ~format:Flow encode_codec ("test", [|1.; 2.; 3.|]) with 10 + | Ok yaml_flow -> 11 + Printf.printf "Length: %d\n" (String.length yaml_flow); 12 + Printf.printf "Repr: %S\n" yaml_flow; 13 + Printf.printf "Output:\n%s" yaml_flow 14 + | Error e -> Printf.printf "Error: %s\n" e
+7 -2
tests/cram/arrays_codec.t
··· 78 78 File "-", line 1, characters 11-22: array<string> 79 79 File "-": in member values of 80 80 File "-", line 1, characters 0-22: Nullable object 81 - YAML: nullable_array: ["hello"; "null"; "world"; "null"; "test"] 81 + YAML: nullable_array: ERROR: Expected string but found null 82 + File "-": 83 + at index 1 of 84 + File "-": array<string> 85 + File "-": in member values of 86 + File "-": Nullable object 82 87 83 88 ================================================================================ 84 89 ERROR HANDLING ··· 112 117 strings: 113 118 - hello 114 119 - world 115 - YAML Flow: {numbers: [1.0, 2.0, 3.0, 4.0, 5.0]strings, [hello, world]} 120 + YAML Flow: {numbers: [1.0, 2.0, 3.0, 4.0, 5.0], strings: [hello, world]} 116 121 117 122 ================================================================================ 118 123 NEGATIVE TESTS - Wrong File Types
+1 -4
tests/cram/complex_codec.t
··· 32 32 33 33 $ test_complex complex-optional ../data/complex/complex_optional.yml 34 34 JSON: complex_optional: host="example.com", port=443, ssl=true, fallbacks=2 35 - YAML: complex_optional: ERROR: Expected array<string> but found sequence 36 - File "-": 37 - File "-": in member fallback_hosts of 38 - File "-": Config object 35 + YAML: complex_optional: host="example.com", port=443, ssl=true, fallbacks=2 39 36 40 37 ================================================================================ 41 38 HETEROGENEOUS DATA
+6 -6
tests/cram/formats_codec.t
··· 31 31 32 32 $ test_formats number-formats ../data/formats/number_formats.yml 33 33 JSON: number_formats: hex=255, octal=63, binary=10 34 - YAML: number_formats: ERROR: Expected number but found scalar 0o77 35 - File "-": 36 - File "-": in member octal of 37 - File "-": Numbers object 34 + YAML: number_formats: hex=255, octal=63, binary=10 38 35 39 36 ================================================================================ 40 37 COMMENTS ··· 56 53 File "-", line 1, characters 10-11: 57 54 File "-": in member value of 58 55 File "-", line 1, characters 0-11: Wrapper object 59 - YAML: empty_document: value=Some("null") 56 + YAML: empty_document: ERROR: Expected string but found null 57 + File "-": 58 + File "-": in member value of 59 + File "-": Wrapper object 60 60 61 61 ================================================================================ 62 62 EXPLICIT TYPE TAGS ··· 85 85 count: 5.0 86 86 87 87 YAML Flow: 88 - {name: test, values: [1.0, 2.0, 3.0]nested, {enabled: true, count: 5.0}} 88 + {name: test, values: [1.0, 2.0, 3.0], nested: {enabled: true, count: 5.0}} 89 89 90 90 91 91 ================================================================================
+1 -4
tests/cram/scalars_codec.t
··· 149 149 JSON number_codec 150 150 decode: 42 151 151 YAML number_codec 152 - decode: ERROR: Expected number but found scalar 0o52 153 - File "-": 154 - File "-": in member value of 155 - File "-": NumberTest object 152 + decode: 42 156 153 157 154 Negative numbers 158 155