Yaml encoder/decoder for OCaml jsont codecs

fixes

+5 -3
lib/yamlt.ml
···
| None -> err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
| String map ->
(* Don't decode null values as strings - they should fail so outer combinators
-
like 'option' or 'any' can handle them properly *)
-
if is_null_scalar value then
+
like 'option' or 'any' can handle them properly.
+
BUT: quoted strings should always be treated as strings, even if they
+
look like null (e.g., "" or "null") *)
+
if style = `Plain && is_null_scalar value then
err_type_mismatch d ev.span t ~fnd:"null"
else
-
(* Strings accept any non-null scalar value *)
+
(* Strings accept quoted scalars or non-null plain scalars *)
map.dec meta value
| Map m ->
(* Handle Map combinators (e.g., from Jsont.option) *)
+8
tests/bin/dune
···
(executable
(name test_some_vs_option)
(libraries yamlt jsont jsont.bytesrw bytesrw))
+
+
(executable
+
(name test_comprehensive)
+
(libraries yamlt jsont jsont.bytesrw bytesrw))
+
+
(executable
+
(name test_flow_newline)
+
(libraries yamlt jsont jsont.bytesrw bytesrw))
+88
tests/bin/test_comprehensive.ml
···
+
let () =
+
(* Test 1: Null handling with option types *)
+
Printf.printf "=== NULL HANDLING ===\n";
+
let opt_codec =
+
Jsont.Object.map ~kind:"Test" (fun v -> v)
+
|> Jsont.Object.mem "value" (Jsont.option Jsont.string) ~enc:(fun v -> v)
+
|> Jsont.Object.finish
+
in
+
+
(match Yamlt.decode_string opt_codec "value: null" with
+
| Ok None -> Printf.printf "✓ Plain 'null' with option codec: None\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string opt_codec "value: hello" with
+
| Ok (Some "hello") -> Printf.printf "✓ Plain 'hello' with option codec: Some(hello)\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
let string_codec =
+
Jsont.Object.map ~kind:"Test" (fun v -> v)
+
|> Jsont.Object.mem "value" Jsont.string ~enc:(fun v -> v)
+
|> Jsont.Object.finish
+
in
+
+
(match Yamlt.decode_string string_codec "value: null" with
+
| Error _ -> Printf.printf "✓ Plain 'null' with string codec: ERROR (expected)\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string string_codec "value: \"\"" with
+
| Ok "" -> Printf.printf "✓ Quoted empty string: \"\"\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string string_codec "value: \"null\"" with
+
| Ok "null" -> Printf.printf "✓ Quoted 'null': \"null\"\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(* Test 2: Number formats *)
+
Printf.printf "\n=== NUMBER FORMATS ===\n";
+
let num_codec =
+
Jsont.Object.map ~kind:"Test" (fun v -> v)
+
|> Jsont.Object.mem "value" Jsont.number ~enc:(fun v -> v)
+
|> Jsont.Object.finish
+
in
+
+
(match Yamlt.decode_string num_codec "value: 0xFF" with
+
| Ok 255. -> Printf.printf "✓ Hex 0xFF: 255\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string num_codec "value: 0o77" with
+
| Ok 63. -> Printf.printf "✓ Octal 0o77: 63\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string num_codec "value: 0b1010" with
+
| Ok 10. -> Printf.printf "✓ Binary 0b1010: 10\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(* Test 3: Optional arrays *)
+
Printf.printf "\n=== OPTIONAL ARRAYS ===\n";
+
let opt_array_codec =
+
Jsont.Object.map ~kind:"Test" (fun v -> v)
+
|> Jsont.Object.opt_mem "values" (Jsont.array Jsont.string) ~enc:(fun v -> v)
+
|> Jsont.Object.finish
+
in
+
+
(match Yamlt.decode_string opt_array_codec "values: [a, b, c]" with
+
| Ok (Some arr) when Array.length arr = 3 ->
+
Printf.printf "✓ Optional array [a, b, c]: Some([3 items])\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(match Yamlt.decode_string opt_array_codec "{}" with
+
| Ok None -> Printf.printf "✓ Missing optional array: None\n"
+
| _ -> Printf.printf "✗ FAIL\n");
+
+
(* Test 4: Flow encoding *)
+
Printf.printf "\n=== FLOW ENCODING ===\n";
+
let encode_codec =
+
Jsont.Object.map ~kind:"Test" (fun name values -> (name, values))
+
|> Jsont.Object.mem "name" Jsont.string ~enc:fst
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:snd
+
|> Jsont.Object.finish
+
in
+
+
(match Yamlt.encode_string ~format:Flow encode_codec ("test", [|1.; 2.; 3.|]) with
+
| Ok yaml_flow when String.equal yaml_flow "{name: test, values: [1.0, 2.0, 3.0]}\n" ->
+
Printf.printf "✓ Flow encoding with comma separator\n"
+
| Ok yaml_flow ->
+
Printf.printf "✗ FAIL: %S\n" yaml_flow
+
| Error e ->
+
Printf.printf "✗ ERROR: %s\n" e)
+14
tests/bin/test_flow_newline.ml
···
+
let () =
+
let encode_codec =
+
Jsont.Object.map ~kind:"Test" (fun name values -> (name, values))
+
|> Jsont.Object.mem "name" Jsont.string ~enc:fst
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:snd
+
|> Jsont.Object.finish
+
in
+
+
match Yamlt.encode_string ~format:Flow encode_codec ("test", [|1.; 2.; 3.|]) with
+
| Ok yaml_flow ->
+
Printf.printf "Length: %d\n" (String.length yaml_flow);
+
Printf.printf "Repr: %S\n" yaml_flow;
+
Printf.printf "Output:\n%s" yaml_flow
+
| Error e -> Printf.printf "Error: %s\n" e
+7 -2
tests/cram/arrays_codec.t
···
File "-", line 1, characters 11-22: array<string>
File "-": in member values of
File "-", line 1, characters 0-22: Nullable object
-
YAML: nullable_array: ["hello"; "null"; "world"; "null"; "test"]
+
YAML: nullable_array: ERROR: Expected string but found null
+
File "-":
+
at index 1 of
+
File "-": array<string>
+
File "-": in member values of
+
File "-": Nullable object
================================================================================
ERROR HANDLING
···
strings:
- hello
- world
-
YAML Flow: {numbers: [1.0, 2.0, 3.0, 4.0, 5.0]strings, [hello, world]}
+
YAML Flow: {numbers: [1.0, 2.0, 3.0, 4.0, 5.0], strings: [hello, world]}
================================================================================
NEGATIVE TESTS - Wrong File Types
+1 -4
tests/cram/complex_codec.t
···
$ test_complex complex-optional ../data/complex/complex_optional.yml
JSON: complex_optional: host="example.com", port=443, ssl=true, fallbacks=2
-
YAML: complex_optional: ERROR: Expected array<string> but found sequence
-
File "-":
-
File "-": in member fallback_hosts of
-
File "-": Config object
+
YAML: complex_optional: host="example.com", port=443, ssl=true, fallbacks=2
================================================================================
HETEROGENEOUS DATA
+6 -6
tests/cram/formats_codec.t
···
$ test_formats number-formats ../data/formats/number_formats.yml
JSON: number_formats: hex=255, octal=63, binary=10
-
YAML: number_formats: ERROR: Expected number but found scalar 0o77
-
File "-":
-
File "-": in member octal of
-
File "-": Numbers object
+
YAML: number_formats: hex=255, octal=63, binary=10
================================================================================
COMMENTS
···
File "-", line 1, characters 10-11:
File "-": in member value of
File "-", line 1, characters 0-11: Wrapper object
-
YAML: empty_document: value=Some("null")
+
YAML: empty_document: ERROR: Expected string but found null
+
File "-":
+
File "-": in member value of
+
File "-": Wrapper object
================================================================================
EXPLICIT TYPE TAGS
···
count: 5.0
YAML Flow:
-
{name: test, values: [1.0, 2.0, 3.0]nested, {enabled: true, count: 5.0}}
+
{name: test, values: [1.0, 2.0, 3.0], nested: {enabled: true, count: 5.0}}
================================================================================
+1 -4
tests/cram/scalars_codec.t
···
JSON number_codec
decode: 42
YAML number_codec
-
decode: ERROR: Expected number but found scalar 0o52
-
File "-":
-
File "-": in member value of
-
File "-": NumberTest object
+
decode: 42
Negative numbers