Yaml encoder/decoder for OCaml jsont codecs

fmt

+19 -1
.gitignore
···
-
_build
+
# OCaml build artifacts
+
_build/
+
*.install
+
*.merlin
+
+
# Third-party sources (fetch locally with opam source)
+
third_party/
+
+
# Symlinked dependencies
ocaml-yamlrw
+
+
# Editor and OS files
+
.DS_Store
+
*.swp
+
*~
+
.vscode/
+
.idea/
+
+
# Opam local switch
+
_opam/
+1
.ocamlformat
···
+
version=0.28.1
+52
.tangled/workflows/build.yml
···
+
when:
+
- event: ["push", "pull_request"]
+
branch: ["main"]
+
+
engine: nixery
+
+
dependencies:
+
nixpkgs:
+
- shell
+
- stdenv
+
- findutils
+
- binutils
+
- libunwind
+
- ncurses
+
- opam
+
- git
+
- gawk
+
- gnupatch
+
- gnum4
+
- gnumake
+
- gnutar
+
- gnused
+
- gnugrep
+
- diffutils
+
- gzip
+
- bzip2
+
- gcc
+
- ocaml
+
+
steps:
+
- name: opam
+
command: |
+
opam init --disable-sandboxing -a -y
+
- name: repo
+
command: |
+
opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git
+
- name: switch
+
command: |
+
opam install . --confirm-level=unsafe-yes --deps-only
+
- name: build
+
command: |
+
opam exec -- dune build
+
- name: switch-test
+
command: |
+
opam install . --confirm-level=unsafe-yes --deps-only --with-test
+
- name: test
+
command: |
+
opam exec -- dune runtest --verbose
+
- name: doc
+
command: |
+
opam install -y odoc
+
opam exec -- dune build @doc
+15
LICENSE.md
···
+
ISC License
+
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
+
+
Permission to use, copy, modify, and distribute this software for any
+
purpose with or without fee is hereby granted, provided that the above
+
copyright notice and this permission notice appear in all copies.
+
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+62
README.md
···
+
# yamlt - YAML codec using Jsont type descriptions
+
+
Yamlt provides YAML streaming encode/decode that interprets Jsont.t type descriptions, allowing the same codec definitions to work for both JSON and YAML.
+
+
## Key Features
+
+
- Use the same Jsont.t codec for both JSON and YAML formats
+
- Streaming encode/decode with configurable depth and node limits
+
- Support for YAML-specific features (scalars, sequences, mappings)
+
- Billion laughs protection with configurable limits
+
- Multiple output formats (block, flow, layout preservation)
+
+
## Usage
+
+
```ocaml
+
(* Define a codec once using Jsont *)
+
module Config = struct
+
type t = { name: string; port: int }
+
let make name port = { name; port }
+
let jsont =
+
Jsont.Object.map ~kind:"Config" make
+
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
+
|> Jsont.Object.mem "port" Jsont.int ~enc:(fun c -> c.port)
+
|> Jsont.Object.finish
+
end
+
+
(* Use the same codec for both JSON and YAML *)
+
let from_json = Jsont_bytesrw.decode_string Config.jsont json_str
+
let from_yaml = Yamlt.decode_string Config.jsont yaml_str
+
```
+
+
For encoding:
+
+
```ocaml
+
(* Encode to YAML with different formats *)
+
let config = Config.make "server" 8080
+
+
(* Block style (default) *)
+
let yaml_block = Yamlt.encode_string Config.jsont config
+
+
(* Flow style (JSON-like) *)
+
let yaml_flow = Yamlt.encode_string ~format:Flow Config.jsont config
+
```
+
+
## Installation
+
+
```
+
opam install yamlt
+
```
+
+
## Documentation
+
+
API documentation is available at https://tangled.org/@anil.recoil.org/ocaml-yamlt or via:
+
+
```
+
opam install yamlt
+
odig doc yamlt
+
```
+
+
## License
+
+
ISC
+5
dune
···
+
; Root dune file
+
+
; Ignore third_party directory (for fetched dependency sources)
+
+
(data_only_dirs third_party)
+10 -1
dune-project
···
(lang dune 3.18)
+
(name yamlt)
(generate_opam_files true)
+
(license ISC)
+
(authors "Anil Madhavapeddy")
+
(homepage "https://tangled.org/@anil.recoil.org/ocaml-yamlt")
+
(maintainers "Anil Madhavapeddy <anil@recoil.org>")
+
(bug_reports "https://tangled.org/@anil.recoil.org/ocaml-yamlt/issues")
+
(maintenance_intent "(latest)")
+
(package
(name yamlt)
(synopsis "YAML codec using Jsont type descriptions")
···
(ocaml (>= 4.14.0))
yamlrw
jsont
-
bytesrw))
+
bytesrw
+
(odoc :with-doc)))
+469 -389
lib/yamlt.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
open Bytesrw
open Jsont.Repr
···
meta_none : Jsont.Meta.t;
}
-
let make_decoder
-
?(locs = false) ?(layout = false) ?(file = "-")
+
let make_decoder ?(locs = false) ?(layout = false) ?(file = "-")
?(max_depth = 100) ?(max_nodes = 10_000_000) parser =
let meta_none = Jsont.Meta.make (Jsont.Textloc.(set_file none) file) in
-
{ parser; file; locs; _layout = layout; max_depth; max_nodes;
-
node_count = 0; current = None;
-
_anchors = Hashtbl.create 16; meta_none }
+
{
+
parser;
+
file;
+
locs;
+
_layout = layout;
+
max_depth;
+
max_nodes;
+
node_count = 0;
+
current = None;
+
_anchors = Hashtbl.create 16;
+
meta_none;
+
}
(* Decoder helpers *)
let check_depth d ~nest =
if nest > d.max_depth then
-
Jsont.Error.msgf Jsont.Meta.none "Maximum nesting depth %d exceeded" d.max_depth
+
Jsont.Error.msgf Jsont.Meta.none "Maximum nesting depth %d exceeded"
+
d.max_depth
let check_nodes d =
d.node_count <- d.node_count + 1;
if d.node_count > d.max_nodes then
-
Jsont.Error.msgf Jsont.Meta.none "Maximum node count %d exceeded" d.max_nodes
+
Jsont.Error.msgf Jsont.Meta.none "Maximum node count %d exceeded"
+
d.max_nodes
let meta_of_span d span =
-
if not d.locs then d.meta_none else
-
let start = span.Span.start and stop = span.Span.stop in
-
let first_byte = start.Position.index in
-
let last_byte = max first_byte (stop.Position.index - 1) in
-
(* line_pos is (line_number, byte_position_of_line_start) *)
-
let first_line = (start.Position.line, start.Position.index - start.Position.column + 1) in
-
let last_line = (stop.Position.line, stop.Position.index - stop.Position.column + 1) in
-
let textloc = Jsont.Textloc.make ~file:d.file
-
~first_byte ~last_byte ~first_line ~last_line in
-
Jsont.Meta.make textloc
+
if not d.locs then d.meta_none
+
else
+
let start = span.Span.start and stop = span.Span.stop in
+
let first_byte = start.Position.index in
+
let last_byte = max first_byte (stop.Position.index - 1) in
+
(* line_pos is (line_number, byte_position_of_line_start) *)
+
let first_line =
+
(start.Position.line, start.Position.index - start.Position.column + 1)
+
in
+
let last_line =
+
(stop.Position.line, stop.Position.index - stop.Position.column + 1)
+
in
+
let textloc =
+
Jsont.Textloc.make ~file:d.file ~first_byte ~last_byte ~first_line
+
~last_line
+
in
+
Jsont.Meta.make textloc
let next_event d =
d.current <- Parser.next d.parser;
d.current
let peek_event d =
-
match d.current with
-
| Some _ -> d.current
-
| None -> next_event d
+
match d.current with Some _ -> d.current | None -> next_event d
-
let skip_event d =
-
d.current <- None
+
let skip_event d = d.current <- None
let _expect_event d pred name =
match peek_event d with
-
| Some ev when pred ev.Event.event -> skip_event d; ev
+
| Some ev when pred ev.Event.event ->
+
skip_event d;
+
ev
| Some ev ->
let span = ev.Event.span in
let meta = meta_of_span d span in
-
Jsont.Error.msgf meta "Expected %s but found %a" name Event.pp ev.Event.event
+
Jsont.Error.msgf meta "Expected %s but found %a" name Event.pp
+
ev.Event.event
| None ->
-
Jsont.Error.msgf Jsont.Meta.none "Expected %s but reached end of stream" name
+
Jsont.Error.msgf Jsont.Meta.none "Expected %s but reached end of stream"
+
name
(* Error helpers *)
···
let err_type_mismatch d span t ~fnd =
let meta = meta_of_span d span in
-
Jsont.Error.msgf meta "Expected %s but found %s"
-
(Jsont.Repr.kinded_sort t) fnd
+
Jsont.Error.msgf meta "Expected %s but found %s" (Jsont.Repr.kinded_sort t)
+
fnd
(* YAML scalar resolution *)
let is_null_scalar s =
-
s = "" || s = "~" ||
-
s = "null" || s = "Null" || s = "NULL"
+
s = "" || s = "~" || s = "null" || s = "Null" || s = "NULL"
let bool_of_scalar_opt s =
match s with
-
| "true" | "True" | "TRUE"
-
| "yes" | "Yes" | "YES"
-
| "on" | "On" | "ON" -> Some true
-
| "false" | "False" | "FALSE"
-
| "no" | "No" | "NO"
-
| "off" | "Off" | "OFF" -> Some false
+
| "true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" ->
+
Some true
+
| "false" | "False" | "FALSE" | "no" | "No" | "NO" | "off" | "Off" | "OFF" ->
+
Some false
| _ -> None
let float_of_scalar_opt s =
···
| "+.inf" | "+.Inf" | "+.INF" -> Some Float.infinity
| "-.inf" | "-.Inf" | "-.INF" -> Some Float.neg_infinity
| ".nan" | ".NaN" | ".NAN" -> Some Float.nan
-
| _ ->
+
| _ -> (
(* Try parsing as number, allowing underscores *)
let s' = String.concat "" (String.split_on_char '_' s) in
(* Try int first (supports 0o, 0x, 0b) then float *)
match int_of_string_opt s' with
| Some i -> Some (float_of_int i)
-
| None -> float_of_string_opt s'
+
| None -> float_of_string_opt s')
let _int_of_scalar_opt s =
(* Handle hex, octal, and regular integers with underscores *)
···
int_of_string_opt s'
(* Decode a scalar value according to expected type *)
-
let rec decode_scalar_as :
-
type a. decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a =
-
fun d ev value style t ->
+
let rec decode_scalar_as : type a.
+
decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a =
+
fun d ev value style t ->
check_nodes d;
let meta = meta_of_span d ev.Event.span in
match t with
| Null map ->
if is_null_scalar value then map.dec meta ()
else err_type_mismatch d ev.span t ~fnd:("scalar " ^ value)
-
| Bool map ->
-
(match bool_of_scalar_opt value with
-
| Some b -> map.dec meta b
-
| None ->
-
(* For explicitly quoted strings, fail *)
-
if style <> `Plain then
-
err_type_mismatch d ev.span t ~fnd:("string " ^ value)
-
else
-
err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
-
| Number map ->
-
(* Handle null -> nan mapping like jsont *)
-
if is_null_scalar value then map.dec meta Float.nan
+
| Bool map -> (
+
match bool_of_scalar_opt value with
+
| Some b -> map.dec meta b
+
| None ->
+
(* For explicitly quoted strings, fail *)
+
if style <> `Plain then
+
err_type_mismatch d ev.span t ~fnd:("string " ^ value)
+
else err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
+
| Number map -> (
+
if
+
(* Handle null -> nan mapping like jsont *)
+
is_null_scalar value
+
then map.dec meta Float.nan
else
-
(match float_of_scalar_opt value with
-
| Some f -> map.dec meta f
-
| None -> err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
+
match float_of_scalar_opt value with
+
| Some f -> map.dec meta f
+
| 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.
···
| Rec lazy_t ->
(* Handle recursive types *)
decode_scalar_as d ev value style (Lazy.force lazy_t)
-
| _ ->
-
err_type_mismatch d ev.span t ~fnd:"scalar"
+
| _ -> err_type_mismatch d ev.span t ~fnd:"scalar"
(* Forward declaration for mutual recursion *)
let rec decode : type a. decoder -> nest:int -> a t -> a =
-
fun d ~nest t ->
+
fun d ~nest t ->
check_depth d ~nest;
match peek_event d with
| None -> Jsont.Error.msgf Jsont.Meta.none "Unexpected end of YAML stream"
-
| Some ev ->
-
match ev.Event.event, t with
+
| Some ev -> (
+
match (ev.Event.event, t) with
(* Scalar events *)
| Event.Scalar { value; style; anchor; _ }, _ ->
skip_event d;
let result = decode_scalar d ~nest ev value style t in
(* Store anchor if present - TODO: implement anchor storage *)
(match anchor with
-
| Some _name ->
-
(* We need generic JSON for anchors - decode as json and convert back *)
-
()
-
| None -> ());
+
| Some _name ->
+
(* We need generic JSON for anchors - decode as json and convert back *)
+
()
+
| None -> ());
result
-
(* Alias *)
| Event.Alias { anchor }, _ ->
skip_event d;
decode_alias d ev anchor t
-
(* Map combinator - must come before specific event matches *)
-
| _, Map m ->
-
m.dec (decode d ~nest m.dom)
-
+
| _, Map m -> m.dec (decode d ~nest m.dom)
(* Recursive types - must come before specific event matches *)
-
| _, Rec lazy_t ->
-
decode d ~nest (Lazy.force lazy_t)
-
+
| _, Rec lazy_t -> decode d ~nest (Lazy.force lazy_t)
(* Sequence -> Array *)
-
| Event.Sequence_start _, Array map ->
-
decode_array d ~nest ev map
-
-
| Event.Sequence_start _, Any map ->
-
decode_any_sequence d ~nest ev t map
-
+
| Event.Sequence_start _, Array map -> decode_array d ~nest ev map
+
| Event.Sequence_start _, Any map -> decode_any_sequence d ~nest ev t map
| Event.Sequence_start _, _ ->
err_type_mismatch d ev.span t ~fnd:"sequence"
-
(* Mapping -> Object *)
-
| Event.Mapping_start _, Object map ->
-
decode_object d ~nest ev map
-
-
| Event.Mapping_start _, Any map ->
-
decode_any_mapping d ~nest ev t map
-
-
| Event.Mapping_start _, _ ->
-
err_type_mismatch d ev.span t ~fnd:"mapping"
-
+
| Event.Mapping_start _, Object map -> decode_object d ~nest ev map
+
| Event.Mapping_start _, Any map -> decode_any_mapping d ~nest ev t map
+
| Event.Mapping_start _, _ -> err_type_mismatch d ev.span t ~fnd:"mapping"
(* Unexpected events *)
| Event.Sequence_end, _ ->
Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected sequence end"
···
| Event.Stream_start _, _ ->
Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected stream start"
| Event.Stream_end, _ ->
-
Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected stream end"
+
Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected stream end")
-
and decode_scalar : type a. decoder -> nest:int -> Event.spanned -> string -> Scalar_style.t -> a t -> a =
-
fun d ~nest ev value style t ->
+
and decode_scalar : type a.
+
decoder -> nest:int -> Event.spanned -> string -> Scalar_style.t -> a t -> a
+
=
+
fun d ~nest ev value style t ->
match t with
| Any map -> decode_any_scalar d ev value style t map
| Map m -> m.dec (decode_scalar d ~nest ev value style m.dom)
| Rec lazy_t -> decode_scalar d ~nest ev value style (Lazy.force lazy_t)
| _ -> decode_scalar_as d ev value style t
-
and decode_any_scalar : type a. decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a any_map -> a =
-
fun d ev value style t map ->
+
and decode_any_scalar : type a.
+
decoder ->
+
Event.spanned ->
+
string ->
+
Scalar_style.t ->
+
a t ->
+
a any_map ->
+
a =
+
fun d ev value style t map ->
check_nodes d;
(* Determine which decoder to use based on scalar content *)
if is_null_scalar value then
match map.dec_null with
| Some t' -> decode_scalar_as d ev value style t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Null
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Null
else if style = `Plain then
(* Try bool, then number, then string *)
match bool_of_scalar_opt value with
-
| Some _ ->
-
(match map.dec_bool with
-
| Some t' -> decode_scalar_as d ev value style t'
-
| None ->
-
match map.dec_string with
-
| Some t' -> decode_scalar_as d ev value style t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Bool)
-
| None ->
+
| Some _ -> (
+
match map.dec_bool with
+
| Some t' -> decode_scalar_as d ev value style t'
+
| None -> (
+
match map.dec_string with
+
| Some t' -> decode_scalar_as d ev value style t'
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t
+
~fnd:Jsont.Sort.Bool))
+
| None -> (
match float_of_scalar_opt value with
-
| Some _ ->
-
(match map.dec_number with
-
| Some t' -> decode_scalar_as d ev value style t'
-
| None ->
-
match map.dec_string with
-
| Some t' -> decode_scalar_as d ev value style t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Number)
-
| None ->
+
| Some _ -> (
+
match map.dec_number with
+
| Some t' -> decode_scalar_as d ev value style t'
+
| None -> (
+
match map.dec_string with
+
| Some t' -> decode_scalar_as d ev value style t'
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t
+
~fnd:Jsont.Sort.Number))
+
| None -> (
(* Plain scalar that's not bool/number -> string *)
match map.dec_string with
| Some t' -> decode_scalar_as d ev value style t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.String
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t
+
~fnd:Jsont.Sort.String))
else
(* Quoted scalars are strings *)
match map.dec_string with
| Some t' -> decode_scalar_as d ev value style t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.String
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.String
and decode_alias : type a. decoder -> Event.spanned -> string -> a t -> a =
-
fun d ev anchor t ->
+
fun d ev anchor t ->
check_nodes d;
match Hashtbl.find_opt d._anchors anchor with
| None ->
let meta = meta_of_span d ev.span in
Jsont.Error.msgf meta "Unknown anchor: %s" anchor
-
| Some json ->
+
| Some json -> (
(* Decode the stored JSON value through the type *)
let t' = Jsont.Repr.unsafe_to_t t in
match Jsont.Json.decode' t' json with
| Ok v -> v
-
| Error e -> raise (Jsont.Error e)
+
| Error e -> raise (Jsont.Error e))
-
and decode_array : type a elt b. decoder -> nest:int -> Event.spanned -> (a, elt, b) array_map -> a =
-
fun d ~nest start_ev map ->
-
skip_event d; (* consume Sequence_start *)
+
and decode_array : type a elt b.
+
decoder -> nest:int -> Event.spanned -> (a, elt, b) array_map -> a =
+
fun d ~nest start_ev map ->
+
skip_event d;
+
(* consume Sequence_start *)
check_nodes d;
let meta = meta_of_span d start_ev.span in
let builder = ref (map.dec_empty ()) in
···
(try
if map.dec_skip i !builder then begin
(* Skip this element by decoding as ignore *)
-
let _ : unit = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) in
+
let _ : unit =
+
decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore)
+
in
()
-
end else begin
+
end
+
else begin
let elt = decode d ~nest:(nest + 1) map.elt in
builder := map.dec_add i elt !builder
end
···
Jsont.Repr.error_push_array meta map (i, imeta) e);
incr idx;
loop ()
-
| None ->
-
Jsont.Error.msgf meta "Unclosed sequence"
+
| None -> Jsont.Error.msgf meta "Unclosed sequence"
in
loop ()
-
and decode_any_sequence : type a. decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
-
fun d ~nest ev t map ->
+
and decode_any_sequence : type a.
+
decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
+
fun d ~nest ev t map ->
match map.dec_array with
-
| Some t' ->
+
| Some t' -> (
(* The t' decoder might be wrapped (e.g., Map for option types)
Directly decode the array and let the wrapper handle it *)
-
(match t' with
-
| Array array_map ->
-
decode_array d ~nest ev array_map
-
| _ ->
-
(* For wrapped types like Map (Array ...), use full decode *)
-
decode d ~nest t')
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Array
+
match t' with
+
| Array array_map -> decode_array d ~nest ev array_map
+
| _ ->
+
(* For wrapped types like Map (Array ...), use full decode *)
+
decode d ~nest t')
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Array
-
and decode_object : type o. decoder -> nest:int -> Event.spanned -> (o, o) object_map -> o =
-
fun d ~nest start_ev map ->
-
skip_event d; (* consume Mapping_start *)
+
and decode_object : type o.
+
decoder -> nest:int -> Event.spanned -> (o, o) object_map -> o =
+
fun d ~nest start_ev map ->
+
skip_event d;
+
(* consume Mapping_start *)
check_nodes d;
let meta = meta_of_span d start_ev.span in
-
let dict = decode_object_members d ~nest meta map String_map.empty Dict.empty in
+
let dict =
+
decode_object_members d ~nest meta map String_map.empty Dict.empty
+
in
let dict = Dict.add object_meta_arg meta dict in
apply_dict map.dec dict
and decode_object_members : type o.
-
decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
-
mem_dec String_map.t -> Dict.t -> Dict.t =
-
fun d ~nest obj_meta map mem_miss dict ->
+
decoder ->
+
nest:int ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
mem_dec String_map.t ->
+
Dict.t ->
+
Dict.t =
+
fun d ~nest obj_meta map mem_miss dict ->
(* Merge expected member decoders *)
let u _ _ _ = assert false in
let mem_miss = String_map.union u mem_miss map.mem_decs in
···
decode_object_cases d ~nest obj_meta map umems cases mem_miss [] dict
and decode_object_basic : type o mems builder.
-
decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
-
(o, mems, builder) unknown_mems ->
-
mem_dec String_map.t -> Dict.t -> Dict.t =
-
fun d ~nest obj_meta map umems mem_miss dict ->
-
let ubuilder = ref (match umems with
-
| Unknown_skip | Unknown_error -> Obj.magic ()
-
| Unknown_keep (mmap, _) -> mmap.dec_empty ()) in
+
decoder ->
+
nest:int ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
(o, mems, builder) unknown_mems ->
+
mem_dec String_map.t ->
+
Dict.t ->
+
Dict.t =
+
fun d ~nest obj_meta map umems mem_miss dict ->
+
let ubuilder =
+
ref
+
(match umems with
+
| Unknown_skip | Unknown_error -> Obj.magic ()
+
| Unknown_keep (mmap, _) -> mmap.dec_empty ())
+
in
let mem_miss = ref mem_miss in
let dict = ref dict in
let rec loop () =
···
let name, name_meta = decode_mapping_key d ev in
(* Look up member decoder *)
(match String_map.find_opt name map.mem_decs with
-
| Some (Mem_dec mem) ->
-
mem_miss := String_map.remove name !mem_miss;
-
(try
-
let v = decode d ~nest:(nest + 1) mem.type' in
-
dict := Dict.add mem.id v !dict
-
with Jsont.Error e ->
-
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
-
| None ->
-
(* Unknown member *)
-
match umems with
-
| Unknown_skip ->
-
let _ : unit = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) in
-
()
-
| Unknown_error ->
-
Jsont.Repr.unexpected_mems_error obj_meta map ~fnd:[(name, name_meta)]
-
| Unknown_keep (mmap, _) ->
-
(try
-
let v = decode d ~nest:(nest + 1) mmap.mems_type in
-
ubuilder := mmap.dec_add name_meta name v !ubuilder
-
with Jsont.Error e ->
-
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e));
+
| Some (Mem_dec mem) -> (
+
mem_miss := String_map.remove name !mem_miss;
+
try
+
let v = decode d ~nest:(nest + 1) mem.type' in
+
dict := Dict.add mem.id v !dict
+
with Jsont.Error e ->
+
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
+
| None -> (
+
(* Unknown member *)
+
match umems with
+
| Unknown_skip ->
+
let _ : unit =
+
decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore)
+
in
+
()
+
| Unknown_error ->
+
Jsont.Repr.unexpected_mems_error obj_meta map
+
~fnd:[ (name, name_meta) ]
+
| Unknown_keep (mmap, _) -> (
+
try
+
let v = decode d ~nest:(nest + 1) mmap.mems_type in
+
ubuilder := mmap.dec_add name_meta name v !ubuilder
+
with Jsont.Error e ->
+
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
+
));
loop ()
-
| None ->
-
Jsont.Error.msgf obj_meta "Unclosed mapping"
+
| None -> Jsont.Error.msgf obj_meta "Unclosed mapping"
in
loop ()
and finish_object : type o mems builder.
-
Jsont.Meta.t -> (o, o) object_map -> (o, mems, builder) unknown_mems ->
-
builder -> mem_dec String_map.t -> Dict.t -> Dict.t =
-
fun meta map umems ubuilder mem_miss dict ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
(o, mems, builder) unknown_mems ->
+
builder ->
+
mem_dec String_map.t ->
+
Dict.t ->
+
Dict.t =
+
fun meta map umems ubuilder mem_miss dict ->
let dict = Dict.add object_meta_arg meta dict in
-
let dict = match umems with
+
let dict =
+
match umems with
| Unknown_skip | Unknown_error -> dict
-
| Unknown_keep (mmap, _) -> Dict.add mmap.id (mmap.dec_finish meta ubuilder) dict
+
| Unknown_keep (mmap, _) ->
+
Dict.add mmap.id (mmap.dec_finish meta ubuilder) dict
in
(* Check for missing required members *)
let add_default _ (Mem_dec mem_map) dict =
···
Jsont.Repr.missing_mems_error meta map ~exp ~fnd:[]
and decode_object_cases : type o cases tag.
-
decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
-
unknown_mems_option ->
-
(o, cases, tag) object_cases ->
-
mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
-
fun d ~nest obj_meta map umems cases mem_miss delayed dict ->
+
decoder ->
+
nest:int ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
unknown_mems_option ->
+
(o, cases, tag) object_cases ->
+
mem_dec String_map.t ->
+
(Jsont.name * Jsont.json) list ->
+
Dict.t ->
+
Dict.t =
+
fun d ~nest obj_meta map umems cases mem_miss delayed dict ->
match peek_event d with
-
| Some { Event.event = Event.Mapping_end; _ } ->
+
| Some { Event.event = Event.Mapping_end; _ } -> (
skip_event d;
(* No tag found - use dec_absent if available *)
-
(match cases.tag.dec_absent with
-
| Some tag ->
-
decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss delayed dict
-
| None ->
-
(* Missing required case tag *)
-
let exp = String_map.singleton cases.tag.name (Mem_dec cases.tag) in
-
let fnd = List.map (fun ((n, _), _) -> n) delayed in
-
Jsont.Repr.missing_mems_error obj_meta map ~exp ~fnd)
+
match cases.tag.dec_absent with
+
| Some tag ->
+
decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss
+
delayed dict
+
| None ->
+
(* Missing required case tag *)
+
let exp = String_map.singleton cases.tag.name (Mem_dec cases.tag) in
+
let fnd = List.map (fun ((n, _), _) -> n) delayed in
+
Jsont.Repr.missing_mems_error obj_meta map ~exp ~fnd)
| Some ev ->
let name, name_meta = decode_mapping_key d ev in
if String.equal name cases.tag.name then begin
(* Found the case tag *)
let tag = decode d ~nest:(nest + 1) cases.tag.type' in
-
decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss delayed dict
-
end else begin
+
decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss
+
delayed dict
+
end
+
else begin
(* Not the case tag - check if known member or delay *)
match String_map.find_opt name map.mem_decs with
-
| Some (Mem_dec mem) ->
+
| Some (Mem_dec mem) -> (
let mem_miss = String_map.remove name mem_miss in
-
(try
-
let v = decode d ~nest:(nest + 1) mem.type' in
-
let dict = Dict.add mem.id v dict in
-
decode_object_cases d ~nest obj_meta map umems cases mem_miss delayed dict
-
with Jsont.Error e ->
-
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
+
try
+
let v = decode d ~nest:(nest + 1) mem.type' in
+
let dict = Dict.add mem.id v dict in
+
decode_object_cases d ~nest obj_meta map umems cases mem_miss
+
delayed dict
+
with Jsont.Error e ->
+
Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
| None ->
(* Unknown member - decode as generic JSON and delay *)
let v = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.json) in
let delayed = ((name, name_meta), v) :: delayed in
-
decode_object_cases d ~nest obj_meta map umems cases mem_miss delayed dict
+
decode_object_cases d ~nest obj_meta map umems cases mem_miss
+
delayed dict
end
-
| None ->
-
Jsont.Error.msgf obj_meta "Unclosed mapping"
+
| None -> Jsont.Error.msgf obj_meta "Unclosed mapping"
and decode_with_case_tag : type o cases tag.
-
decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
-
unknown_mems_option ->
-
(o, cases, tag) object_cases -> tag ->
-
mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
-
fun d ~nest obj_meta map umems cases tag mem_miss delayed dict ->
+
decoder ->
+
nest:int ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
unknown_mems_option ->
+
(o, cases, tag) object_cases ->
+
tag ->
+
mem_dec String_map.t ->
+
(Jsont.name * Jsont.json) list ->
+
Dict.t ->
+
Dict.t =
+
fun d ~nest obj_meta map umems cases tag mem_miss delayed dict ->
let eq_tag (Case c) = cases.tag_compare c.tag tag = 0 in
match List.find_opt eq_tag cases.cases with
-
| None ->
-
Jsont.Repr.unexpected_case_tag_error obj_meta map cases tag
+
| None -> Jsont.Repr.unexpected_case_tag_error obj_meta map cases tag
| Some (Case case) ->
(* Continue decoding with the case's object map *)
-
let case_dict = decode_case_remaining d ~nest obj_meta case.object_map
-
umems mem_miss delayed dict in
+
let case_dict =
+
decode_case_remaining d ~nest obj_meta case.object_map umems mem_miss
+
delayed dict
+
in
let case_value = apply_dict case.object_map.dec case_dict in
Dict.add cases.id (case.dec case_value) dict
and decode_case_remaining : type o.
-
decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
-
unknown_mems_option ->
-
mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
-
fun d ~nest obj_meta case_map _umems mem_miss delayed dict ->
+
decoder ->
+
nest:int ->
+
Jsont.Meta.t ->
+
(o, o) object_map ->
+
unknown_mems_option ->
+
mem_dec String_map.t ->
+
(Jsont.name * Jsont.json) list ->
+
Dict.t ->
+
Dict.t =
+
fun d ~nest obj_meta case_map _umems mem_miss delayed dict ->
(* First, process delayed members against the case map *)
let u _ _ _ = assert false in
let mem_miss = String_map.union u mem_miss case_map.mem_decs in
-
let dict, mem_miss = List.fold_left (fun (dict, mem_miss) ((name, meta), json) ->
-
match String_map.find_opt name case_map.mem_decs with
-
| Some (Mem_dec mem) ->
-
let t' = Jsont.Repr.unsafe_to_t mem.type' in
-
(match Jsont.Json.decode' t' json with
-
| Ok v ->
-
let dict = Dict.add mem.id v dict in
-
let mem_miss = String_map.remove name mem_miss in
-
(dict, mem_miss)
-
| Error e ->
-
Jsont.Repr.error_push_object obj_meta case_map (name, meta) e)
-
| None ->
-
(* Unknown for case too - skip them *)
-
(dict, mem_miss)
-
) (dict, mem_miss) delayed in
+
let dict, mem_miss =
+
List.fold_left
+
(fun (dict, mem_miss) ((name, meta), json) ->
+
match String_map.find_opt name case_map.mem_decs with
+
| Some (Mem_dec mem) -> (
+
let t' = Jsont.Repr.unsafe_to_t mem.type' in
+
match Jsont.Json.decode' t' json with
+
| Ok v ->
+
let dict = Dict.add mem.id v dict in
+
let mem_miss = String_map.remove name mem_miss in
+
(dict, mem_miss)
+
| Error e ->
+
Jsont.Repr.error_push_object obj_meta case_map (name, meta) e)
+
| None ->
+
(* Unknown for case too - skip them *)
+
(dict, mem_miss))
+
(dict, mem_miss) delayed
+
in
(* Then continue reading remaining members using case's own unknown handling *)
match case_map.shape with
| Object_basic case_umems ->
···
(* Nested cases shouldn't happen - use skip for safety *)
decode_object_basic d ~nest obj_meta case_map Unknown_skip mem_miss dict
-
and decode_any_mapping : type a. decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
-
fun d ~nest ev t map ->
+
and decode_any_mapping : type a.
+
decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
+
fun d ~nest ev t map ->
match map.dec_object with
| Some t' -> decode d ~nest t'
-
| None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Object
+
| None ->
+
Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Object
and decode_mapping_key : decoder -> Event.spanned -> string * Jsont.Meta.t =
-
fun d ev ->
+
fun d ev ->
match ev.Event.event with
| Event.Scalar { value; _ } ->
skip_event d;
···
let skip_to_content d =
let rec loop () =
match peek_event d with
-
| Some { Event.event = Event.Stream_start _; _ } -> skip_event d; loop ()
-
| Some { Event.event = Event.Document_start _; _ } -> skip_event d; loop ()
+
| Some { Event.event = Event.Stream_start _; _ } ->
+
skip_event d;
+
loop ()
+
| Some { Event.event = Event.Document_start _; _ } ->
+
skip_event d;
+
loop ()
| _ -> ()
in
loop ()
···
let skip_end_wrappers d =
let rec loop () =
match peek_event d with
-
| Some { Event.event = Event.Document_end _; _ } -> skip_event d; loop ()
-
| Some { Event.event = Event.Stream_end; _ } -> skip_event d; loop ()
+
| Some { Event.event = Event.Document_end _; _ } ->
+
skip_event d;
+
loop ()
+
| Some { Event.event = Event.Stream_end; _ } ->
+
skip_event d;
+
loop ()
| None -> ()
| Some ev ->
let meta = meta_of_span d ev.span in
-
Jsont.Error.msgf meta "Expected end of document but found %a" Event.pp ev.event
+
Jsont.Error.msgf meta "Expected end of document but found %a" Event.pp
+
ev.event
in
loop ()
···
scalar_style : Scalar_style.t;
}
-
let make_encoder
-
?(format = Block) ?(indent = 2) ?(explicit_doc = false)
+
let make_encoder ?(format = Block) ?(indent = 2) ?(explicit_doc = false)
?(scalar_style = `Any) emitter =
{ emitter; format; _indent = indent; explicit_doc; scalar_style }
···
(* Encode null *)
let encode_null e _meta =
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = "null";
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
})
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = "null";
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
})
(* Encode boolean *)
let encode_bool e _meta b =
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = if b then "true" else "false";
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
})
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = (if b then "true" else "false");
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
})
(* Encode number *)
let encode_number e _meta f =
···
| FP_nan -> ".nan"
| FP_infinite -> if f > 0.0 then ".inf" else "-.inf"
| _ ->
-
if Float.is_integer f && Float.abs f < 1e15 then
-
Printf.sprintf "%.0f" f
-
else
-
Printf.sprintf "%g" f
+
if Float.is_integer f && Float.abs f < 1e15 then Printf.sprintf "%.0f" f
+
else Printf.sprintf "%g" f
in
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
})
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
})
(* Encode string *)
let encode_string e _meta s =
let style = choose_scalar_style ~preferred:e.scalar_style s in
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = s;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style;
-
})
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = s;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style;
+
})
let rec encode : type a. encoder -> a t -> a -> unit =
-
fun e t v ->
+
fun e t v ->
match t with
| Null map ->
let meta = map.enc_meta v in
let () = map.enc v in
encode_null e meta
-
| Bool map ->
let meta = map.enc_meta v in
let b = map.enc v in
encode_bool e meta b
-
| Number map ->
let meta = map.enc_meta v in
let f = map.enc v in
encode_number e meta f
-
| String map ->
let meta = map.enc_meta v in
let s = map.enc v in
encode_string e meta s
-
-
| Array map ->
-
encode_array e map v
-
-
| Object map ->
-
encode_object e map v
-
+
| Array map -> encode_array e map v
+
| Object map -> encode_object e map v
| Any map ->
let t' = map.enc v in
encode e t' v
-
-
| Map m ->
-
encode e m.dom (m.enc v)
-
-
| Rec lazy_t ->
-
encode e (Lazy.force lazy_t) v
+
| Map m -> encode e m.dom (m.enc v)
+
| Rec lazy_t -> encode e (Lazy.force lazy_t) v
and encode_array : type a elt b. encoder -> (a, elt, b) array_map -> a -> unit =
-
fun e map v ->
+
fun e map v ->
let style = layout_style_of_format e.format in
-
Emitter.emit e.emitter (Event.Sequence_start {
-
anchor = None;
-
tag = None;
-
implicit = true;
-
style;
-
});
-
let _ = map.enc (fun () _idx elt ->
-
encode e map.elt elt;
-
()
-
) () v in
+
Emitter.emit e.emitter
+
(Event.Sequence_start { anchor = None; tag = None; implicit = true; style });
+
let _ =
+
map.enc
+
(fun () _idx elt ->
+
encode e map.elt elt;
+
())
+
() v
+
in
Emitter.emit e.emitter Event.Sequence_end
and encode_object : type o. encoder -> (o, o) object_map -> o -> unit =
-
fun e map v ->
+
fun e map v ->
let style = layout_style_of_format e.format in
-
Emitter.emit e.emitter (Event.Mapping_start {
-
anchor = None;
-
tag = None;
-
implicit = true;
-
style;
-
});
+
Emitter.emit e.emitter
+
(Event.Mapping_start { anchor = None; tag = None; implicit = true; style });
(* Encode each member *)
-
List.iter (fun (Mem_enc mem) ->
-
let mem_v = mem.enc v in
-
if not (mem.enc_omit mem_v) then begin
-
(* Emit key *)
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = mem.name;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
});
-
(* Emit value *)
-
encode e mem.type' mem_v
-
end
-
) map.mem_encs;
+
List.iter
+
(fun (Mem_enc mem) ->
+
let mem_v = mem.enc v in
+
if not (mem.enc_omit mem_v) then begin
+
(* Emit key *)
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = mem.name;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
});
+
(* Emit value *)
+
encode e mem.type' mem_v
+
end)
+
map.mem_encs;
(* Handle case objects *)
(match map.shape with
-
| Object_basic _ -> ()
-
| Object_cases (_, cases) ->
-
let Case_value (case_map, case_v) = cases.enc_case (cases.enc v) in
-
(* Emit case tag *)
-
if not (cases.tag.enc_omit (case_map.tag)) then begin
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = cases.tag.name;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
});
-
encode e cases.tag.type' case_map.tag
-
end;
-
(* Emit case members *)
-
List.iter (fun (Mem_enc mem) ->
-
let mem_v = mem.enc case_v in
-
if not (mem.enc_omit mem_v) then begin
-
Emitter.emit e.emitter (Event.Scalar {
-
anchor = None;
-
tag = None;
-
value = mem.name;
-
plain_implicit = true;
-
quoted_implicit = true;
-
style = `Plain;
-
});
-
encode e mem.type' mem_v
-
end
-
) case_map.object_map.mem_encs);
+
| Object_basic _ -> ()
+
| Object_cases (_, cases) ->
+
let (Case_value (case_map, case_v)) = cases.enc_case (cases.enc v) in
+
(* Emit case tag *)
+
if not (cases.tag.enc_omit case_map.tag) then begin
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = cases.tag.name;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
});
+
encode e cases.tag.type' case_map.tag
+
end;
+
(* Emit case members *)
+
List.iter
+
(fun (Mem_enc mem) ->
+
let mem_v = mem.enc case_v in
+
if not (mem.enc_omit mem_v) then begin
+
Emitter.emit e.emitter
+
(Event.Scalar
+
{
+
anchor = None;
+
tag = None;
+
value = mem.name;
+
plain_implicit = true;
+
quoted_implicit = true;
+
style = `Plain;
+
});
+
encode e mem.type' mem_v
+
end)
+
case_map.object_map.mem_encs);
Emitter.emit e.emitter Event.Mapping_end
(* Public encode API *)
let encode' ?buf:_ ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer =
-
let config = {
-
Emitter.default_config with
-
indent = Option.value ~default:2 indent;
-
layout_style = (match format with
-
| Some Flow -> `Flow
-
| _ -> `Block);
-
} in
+
let config =
+
{
+
Emitter.default_config with
+
indent = Option.value ~default:2 indent;
+
layout_style = (match format with Some Flow -> `Flow | _ -> `Block);
+
}
+
in
let emitter = Emitter.of_writer ~config writer in
let e = make_encoder ?format ?indent ?explicit_doc ?scalar_style emitter in
try
Emitter.emit e.emitter (Event.Stream_start { encoding = `Utf8 });
-
Emitter.emit e.emitter (Event.Document_start {
-
version = None;
-
implicit = not e.explicit_doc;
-
});
+
Emitter.emit e.emitter
+
(Event.Document_start { version = None; implicit = not e.explicit_doc });
let t' = Jsont.Repr.of_t t in
encode e t' v;
-
Emitter.emit e.emitter (Event.Document_end { implicit = not e.explicit_doc });
+
Emitter.emit e.emitter
+
(Event.Document_end { implicit = not e.explicit_doc });
Emitter.emit e.emitter Event.Stream_end;
if eod then Emitter.flush e.emitter;
Ok ()
···
let encode_string' ?buf ?format ?indent ?explicit_doc ?scalar_style t v =
let b = Buffer.create 256 in
let writer = Bytes.Writer.of_buffer b in
-
match encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod:true writer with
+
match
+
encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod:true
+
writer
+
with
| Ok () -> Ok (Buffer.contents b)
| Error e -> Error e
···
(* Recode *)
-
let recode ?layout ?locs ?file ?max_depth ?max_nodes
-
?buf ?format ?indent ?explicit_doc ?scalar_style t reader writer ~eod =
-
let format = match layout, format with
-
| Some true, None -> Some Layout
-
| _, f -> f
+
let recode ?layout ?locs ?file ?max_depth ?max_nodes ?buf ?format ?indent
+
?explicit_doc ?scalar_style t reader writer ~eod =
+
let format =
+
match (layout, format) with Some true, None -> Some Layout | _, f -> f
in
-
let layout = match layout, format with
-
| None, Some Layout -> Some true
-
| l, _ -> l
+
let layout =
+
match (layout, format) with None, Some Layout -> Some true | l, _ -> l
in
match decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader with
-
| Ok v -> encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer
+
| Ok v ->
+
encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer
| Error e -> Error (Jsont.Error.to_string e)
-
let recode_string ?layout ?locs ?file ?max_depth ?max_nodes
-
?buf ?format ?indent ?explicit_doc ?scalar_style t s =
-
let format = match layout, format with
-
| Some true, None -> Some Layout
-
| _, f -> f
+
let recode_string ?layout ?locs ?file ?max_depth ?max_nodes ?buf ?format ?indent
+
?explicit_doc ?scalar_style t s =
+
let format =
+
match (layout, format) with Some true, None -> Some Layout | _, f -> f
in
-
let layout = match layout, format with
-
| None, Some Layout -> Some true
-
| l, _ -> l
+
let layout =
+
match (layout, format) with None, Some Layout -> Some true | l, _ -> l
in
match decode_string' ?layout ?locs ?file ?max_depth ?max_nodes t s with
| Ok v -> encode_string ?buf ?format ?indent ?explicit_doc ?scalar_style t v
+149 -91
lib/yamlt.mli
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** YAML codec using Jsont type descriptions.
-
This module provides YAML streaming encode/decode that interprets
-
{!Jsont.t} type descriptions, allowing the same codec definitions
-
to work for both JSON and YAML.
+
This module provides YAML streaming encode/decode that interprets {!Jsont.t}
+
type descriptions, allowing the same codec definitions to work for both JSON
+
and YAML.
{b Example:}
{[
(* Define a codec once using Jsont *)
module Config = struct
-
type t = { name: string; port: int }
+
type t = { name : string; port : int }
+
let make name port = { name; port }
+
let jsont =
Jsont.Object.map ~kind:"Config" make
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
···
]}
See notes about {{!yaml_mapping}YAML to JSON mapping} and
-
{{!yaml_scalars}YAML scalar resolution}.
-
*)
+
{{!yaml_scalars}YAML scalar resolution}. *)
open Bytesrw
(** {1:decode Decode} *)
val decode :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
'a Jsont.t -> Bytes.Reader.t -> ('a, string) result
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
'a Jsont.t ->
+
Bytes.Reader.t ->
+
('a, string) result
(** [decode t r] decodes a value from YAML reader [r] according to type [t].
-
{ul
-
{- If [layout] is [true], style information is preserved in {!Jsont.Meta.t}
-
values (for potential round-tripping). Defaults to [false].}
-
{- If [locs] is [true], source locations are preserved in {!Jsont.Meta.t}
-
values and error messages are precisely located. Defaults to [false].}
-
{- [file] is the file path for error messages.
-
Defaults to {!Jsont.Textloc.file_none}.}
-
{- [max_depth] limits nesting depth to prevent stack overflow
-
(billion laughs protection). Defaults to [100].}
-
{- [max_nodes] limits total decoded nodes
-
(billion laughs protection). Defaults to [10_000_000].}}
+
- If [layout] is [true], style information is preserved in {!Jsont.Meta.t}
+
values (for potential round-tripping). Defaults to [false].
+
- If [locs] is [true], source locations are preserved in {!Jsont.Meta.t}
+
values and error messages are precisely located. Defaults to [false].
+
- [file] is the file path for error messages. Defaults to
+
{!Jsont.Textloc.file_none}.
+
- [max_depth] limits nesting depth to prevent stack overflow (billion laughs
+
protection). Defaults to [100].
+
- [max_nodes] limits total decoded nodes (billion laughs protection).
+
Defaults to [10_000_000].
-
The YAML input must contain exactly one document. Multi-document
-
streams are not supported; use {!decode_all} for those. *)
+
The YAML input must contain exactly one document. Multi-document streams are
+
not supported; use {!decode_all} for those. *)
val decode' :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
'a Jsont.t -> Bytes.Reader.t -> ('a, Jsont.Error.t) result
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
'a Jsont.t ->
+
Bytes.Reader.t ->
+
('a, Jsont.Error.t) result
(** [decode'] is like {!val-decode} but preserves the error structure. *)
val decode_string :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
'a Jsont.t -> string -> ('a, string) result
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
'a Jsont.t ->
+
string ->
+
('a, string) result
(** [decode_string] is like {!val-decode} but decodes directly from a string. *)
val decode_string' :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
'a Jsont.t -> string -> ('a, Jsont.Error.t) result
-
(** [decode_string'] is like {!val-decode'} but decodes directly from a string. *)
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
'a Jsont.t ->
+
string ->
+
('a, Jsont.Error.t) result
+
(** [decode_string'] is like {!val-decode'} but decodes directly from a string.
+
*)
(** {1:encode Encode} *)
(** YAML output format. *)
type yaml_format =
-
| Block (** Block style (indented) - default. Clean, readable YAML. *)
-
| Flow (** Flow style (JSON-like). Compact, single-line collections. *)
+
| Block (** Block style (indented) - default. Clean, readable YAML. *)
+
| Flow (** Flow style (JSON-like). Compact, single-line collections. *)
| Layout (** Preserve layout from {!Jsont.Meta.t} when available. *)
val encode :
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> 'a -> eod:bool -> Bytes.Writer.t -> (unit, string) result
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
'a ->
+
eod:bool ->
+
Bytes.Writer.t ->
+
(unit, string) result
(** [encode t v w] encodes value [v] according to type [t] to YAML on [w].
-
{ul
-
{- If [buf] is specified, it is used as a buffer for output slices.
-
Defaults to a buffer of length {!Bytesrw.Bytes.Writer.slice_length}[ w].}
-
{- [format] controls the output style. Defaults to {!Block}.}
-
{- [indent] is the indentation width in spaces. Defaults to [2].}
-
{- [explicit_doc] if [true], emits explicit document markers
-
([---] and [...]). Defaults to [false].}
-
{- [scalar_style] is the preferred style for string scalars.
-
Defaults to [`Any] (auto-detect based on content).}
-
{- [eod] indicates whether {!Bytesrw.Bytes.Slice.eod} should be
-
written on [w] after encoding.}} *)
+
- If [buf] is specified, it is used as a buffer for output slices. Defaults
+
to a buffer of length {!Bytesrw.Bytes.Writer.slice_length}[ w].
+
- [format] controls the output style. Defaults to {!Block}.
+
- [indent] is the indentation width in spaces. Defaults to [2].
+
- [explicit_doc] if [true], emits explicit document markers ([---] and
+
[...]). Defaults to [false].
+
- [scalar_style] is the preferred style for string scalars. Defaults to
+
[`Any] (auto-detect based on content).
+
- [eod] indicates whether {!Bytesrw.Bytes.Slice.eod} should be written on
+
[w] after encoding. *)
val encode' :
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> 'a -> eod:bool -> Bytes.Writer.t -> (unit, Jsont.Error.t) result
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
'a ->
+
eod:bool ->
+
Bytes.Writer.t ->
+
(unit, Jsont.Error.t) result
(** [encode'] is like {!val-encode} but preserves the error structure. *)
val encode_string :
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> 'a -> (string, string) result
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
'a ->
+
(string, string) result
(** [encode_string] is like {!val-encode} but writes to a string. *)
val encode_string' :
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> 'a -> (string, Jsont.Error.t) result
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
'a ->
+
(string, Jsont.Error.t) result
(** [encode_string'] is like {!val-encode'} but writes to a string. *)
(** {1:recode Recode}
The defaults in these functions are those of {!val-decode} and
-
{!val-encode}, except if [layout] is [true], [format] defaults to
-
{!Layout} and vice-versa. *)
+
{!val-encode}, except if [layout] is [true], [format] defaults to {!Layout}
+
and vice-versa. *)
val recode :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> Bytes.Reader.t -> Bytes.Writer.t -> eod:bool ->
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
Bytes.Reader.t ->
+
Bytes.Writer.t ->
+
eod:bool ->
(unit, string) result
(** [recode t r w] is {!val-decode} followed by {!val-encode}. *)
val recode_string :
-
?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
-
?max_depth:int -> ?max_nodes:int ->
-
?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
-
?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
-
'a Jsont.t -> string -> (string, string) result
+
?layout:bool ->
+
?locs:bool ->
+
?file:Jsont.Textloc.fpath ->
+
?max_depth:int ->
+
?max_nodes:int ->
+
?buf:Stdlib.Bytes.t ->
+
?format:yaml_format ->
+
?indent:int ->
+
?explicit_doc:bool ->
+
?scalar_style:Yamlrw.Scalar_style.t ->
+
'a Jsont.t ->
+
string ->
+
(string, string) result
(** [recode_string] is like {!val-recode} but operates on strings. *)
(** {1:yaml_mapping YAML to JSON Mapping}
-
YAML is a superset of JSON. This module maps YAML structures to
-
the JSON data model that {!Jsont.t} describes:
+
YAML is a superset of JSON. This module maps YAML structures to the JSON
+
data model that {!Jsont.t} describes:
-
{ul
-
{- YAML scalars map to JSON null, boolean, number, or string
-
depending on content and the expected type}
-
{- YAML sequences map to JSON arrays}
-
{- YAML mappings map to JSON objects (keys must be strings)}
-
{- YAML aliases are resolved during decoding}
-
{- YAML tags are used to guide type resolution when present}}
+
- YAML scalars map to JSON null, boolean, number, or string depending on
+
content and the expected type
+
- YAML sequences map to JSON arrays
+
- YAML mappings map to JSON objects (keys must be strings)
+
- YAML aliases are resolved during decoding
+
- YAML tags are used to guide type resolution when present
{b Limitations:}
-
{ul
-
{- Only string keys are supported in mappings (JSON object compatibility)}
-
{- Anchors and aliases are resolved; the alias structure is not preserved}
-
{- Multi-document streams require {!decode_all}}} *)
+
- Only string keys are supported in mappings (JSON object compatibility)
+
- Anchors and aliases are resolved; the alias structure is not preserved
+
- Multi-document streams require {!decode_all} *)
(** {1:yaml_scalars YAML Scalar Resolution}
···
{b Null:} [null], [Null], [NULL], [~], or empty string
-
{b Boolean:} [true], [True], [TRUE], [false], [False], [FALSE],
-
[yes], [Yes], [YES], [no], [No], [NO], [on], [On], [ON],
-
[off], [Off], [OFF]
+
{b Boolean:} [true], [True], [TRUE], [false], [False], [FALSE], [yes],
+
[Yes], [YES], [no], [No], [NO], [on], [On], [ON], [off], [Off], [OFF]
{b Number:} Decimal integers, floats, hex ([0x...]), octal ([0o...]),
infinity ([.inf], [-.inf]), NaN ([.nan])
{b String:} Anything else, or explicitly quoted scalars
-
When decoding against a specific {!Jsont.t} type, the expected type
-
takes precedence over automatic resolution. For example, decoding
-
["yes"] against {!Jsont.string} yields the string ["yes"], not [true]. *)
+
When decoding against a specific {!Jsont.t} type, the expected type takes
+
precedence over automatic resolution. For example, decoding ["yes"] against
+
{!Jsont.string} yields the string ["yes"], not [true]. *)
+4 -1
tests/bin/dune
···
(name test_edge)
(public_name test_edge)
(libraries yamlt jsont jsont.bytesrw bytesrw))
-
(executable (name test_null_fix) (libraries yamlt jsont jsont.bytesrw bytesrw))
+
+
(executable
+
(name test_null_fix)
+
(libraries yamlt jsont jsont.bytesrw bytesrw))
(executable
(name test_null_complete)
+13 -10
tests/bin/test_array_variants.ml
···
let () =
let codec1 =
Jsont.Object.map ~kind:"Test" (fun arr -> arr)
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.string) ~enc:(fun arr -> arr)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.string) ~enc:(fun arr ->
+
arr)
|> Jsont.Object.finish
in
···
Printf.printf "Test 1: Non-optional array:\n";
(match Yamlt.decode_string codec1 yaml1 with
-
| Ok arr -> Printf.printf "Result: [%d items]\n" (Array.length arr)
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok arr -> Printf.printf "Result: [%d items]\n" (Array.length arr)
+
| Error e -> Printf.printf "Error: %s\n" e);
let codec2 =
Jsont.Object.map ~kind:"Test" (fun arr -> arr)
-
|> Jsont.Object.mem "values" (Jsont.option (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
+
|> Jsont.Object.mem "values"
+
(Jsont.option (Jsont.array Jsont.string))
+
~enc:(fun arr -> arr)
|> Jsont.Object.finish
in
Printf.printf "\nTest 2: Jsont.option (Jsont.array):\n";
-
(match Yamlt.decode_string codec2 yaml1 with
-
| Ok arr ->
-
(match arr with
-
| None -> Printf.printf "Result: None\n"
-
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
-
| Error e -> Printf.printf "Error: %s\n" e)
+
match Yamlt.decode_string codec2 yaml1 with
+
| Ok arr -> (
+
match arr with
+
| None -> Printf.printf "Result: None\n"
+
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
+
| Error e -> Printf.printf "Error: %s\n" e
+74 -84
tests/bin/test_arrays.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test array codec functionality with Yamlt *)
-
(* Helper to read file *)
let read_file path =
···
(* Test: Simple int array *)
let test_int_array file =
let module M = struct
-
type numbers = { values: int array }
+
type numbers = { values : int array }
let numbers_codec =
Jsont.Object.map ~kind:"Numbers" (fun values -> { values })
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n -> n.values)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n ->
+
n.values)
|> Jsont.Object.finish
let show n =
-
Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map string_of_int n.values)))
+
Printf.sprintf "[%s]"
+
(String.concat "; " (Array.to_list (Array.map string_of_int n.values)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
···
(* Test: String array *)
let test_string_array file =
let module M = struct
-
type tags = { items: string array }
+
type tags = { items : string array }
let tags_codec =
Jsont.Object.map ~kind:"Tags" (fun items -> { items })
-
|> Jsont.Object.mem "items" (Jsont.array Jsont.string) ~enc:(fun t -> t.items)
+
|> Jsont.Object.mem "items" (Jsont.array Jsont.string) ~enc:(fun t ->
+
t.items)
|> Jsont.Object.finish
let show t =
-
Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map (Printf.sprintf "%S") t.items)))
+
Printf.sprintf "[%s]"
+
(String.concat "; "
+
(Array.to_list (Array.map (Printf.sprintf "%S") t.items)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.tags_codec json in
···
(* Test: Float/number array *)
let test_float_array file =
let module M = struct
-
type measurements = { values: float array }
+
type measurements = { values : float array }
let measurements_codec =
Jsont.Object.map ~kind:"Measurements" (fun values -> { values })
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:(fun m -> m.values)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:(fun m ->
+
m.values)
|> Jsont.Object.finish
let show m =
Printf.sprintf "[%s]"
-
(String.concat "; " (Array.to_list (Array.map (Printf.sprintf "%.2f") m.values)))
+
(String.concat "; "
+
(Array.to_list (Array.map (Printf.sprintf "%.2f") m.values)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.measurements_codec json in
···
(* Test: Empty array *)
let test_empty_array file =
let module M = struct
-
type empty = { items: int array }
+
type empty = { items : int array }
let empty_codec =
Jsont.Object.map ~kind:"Empty" (fun items -> { items })
-
|> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun e -> e.items)
+
|> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun e ->
+
e.items)
|> Jsont.Object.finish
-
let show e =
-
Printf.sprintf "length=%d" (Stdlib.Array.length e.items)
+
let show e = Printf.sprintf "length=%d" (Stdlib.Array.length e.items)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.empty_codec json in
···
(* Test: Array of objects *)
let test_object_array file =
let module M = struct
-
type person = { name: string; age: int }
-
type people = { persons: person array }
+
type person = { name : string; age : int }
+
type people = { persons : person array }
let person_codec =
Jsont.Object.map ~kind:"Person" (fun name age -> { name; age })
···
let people_codec =
Jsont.Object.map ~kind:"People" (fun persons -> { persons })
-
|> Jsont.Object.mem "persons" (Jsont.array person_codec) ~enc:(fun p -> p.persons)
+
|> Jsont.Object.mem "persons" (Jsont.array person_codec) ~enc:(fun p ->
+
p.persons)
|> Jsont.Object.finish
let show_person p = Printf.sprintf "{%s,%d}" p.name p.age
+
let show ps =
Printf.sprintf "[%s]"
(String.concat "; " (Array.to_list (Array.map show_person ps.persons)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.people_codec json in
···
(* Test: Nested arrays *)
let test_nested_arrays file =
let module M = struct
-
type matrix = { data: int array array }
+
type matrix = { data : int array array }
let matrix_codec =
Jsont.Object.map ~kind:"Matrix" (fun data -> { data })
-
|> Jsont.Object.mem "data" (Jsont.array (Jsont.array Jsont.int))
-
~enc:(fun m -> m.data)
+
|> Jsont.Object.mem "data"
+
(Jsont.array (Jsont.array Jsont.int))
+
~enc:(fun m -> m.data)
|> Jsont.Object.finish
let show_row row =
-
Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map string_of_int row)))
+
Printf.sprintf "[%s]"
+
(String.concat "; " (Array.to_list (Array.map string_of_int row)))
let show m =
-
Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map show_row m.data)))
+
Printf.sprintf "[%s]"
+
(String.concat "; " (Array.to_list (Array.map show_row m.data)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.matrix_codec json in
···
(* Test: Mixed types in array (should fail with homogeneous codec) *)
let test_type_mismatch file =
let module M = struct
-
type numbers = { values: int array }
+
type numbers = { values : int array }
let numbers_codec =
Jsont.Object.map ~kind:"Numbers" (fun values -> { values })
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n -> n.values)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n ->
+
n.values)
|> Jsont.Object.finish
end in
-
let yaml = read_file file in
let result = Yamlt.decode_string M.numbers_codec yaml in
match result with
···
(* Test: Bool array *)
let test_bool_array file =
let module M = struct
-
type flags = { values: bool array }
+
type flags = { values : bool array }
let flags_codec =
Jsont.Object.map ~kind:"Flags" (fun values -> { values })
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.bool) ~enc:(fun f -> f.values)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.bool) ~enc:(fun f ->
+
f.values)
|> Jsont.Object.finish
let show f =
Printf.sprintf "[%s]"
(String.concat "; " (Array.to_list (Array.map string_of_bool f.values)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.flags_codec json in
···
(* Test: Array with nulls *)
let test_nullable_array file =
let module M = struct
-
type nullable = { values: string option array }
+
type nullable = { values : string option array }
let nullable_codec =
Jsont.Object.map ~kind:"Nullable" (fun values -> { values })
-
|> Jsont.Object.mem "values" (Jsont.array (Jsont.some Jsont.string))
-
~enc:(fun n -> n.values)
+
|> Jsont.Object.mem "values"
+
(Jsont.array (Jsont.some Jsont.string))
+
~enc:(fun n -> n.values)
|> Jsont.Object.finish
-
let show_opt = function
-
| None -> "null"
-
| Some s -> Printf.sprintf "%S" s
+
let show_opt = function None -> "null" | Some s -> Printf.sprintf "%S" s
let show n =
-
Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map show_opt n.values)))
+
Printf.sprintf "[%s]"
+
(String.concat "; " (Array.to_list (Array.map show_opt n.values)))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.nullable_codec json in
···
(* Test: Encoding arrays to different formats *)
let test_encode_arrays () =
let module M = struct
-
type data = { numbers: int array; strings: string array }
+
type data = { numbers : int array; strings : string array }
let data_codec =
-
Jsont.Object.map ~kind:"Data" (fun numbers strings -> { numbers; strings })
-
|> Jsont.Object.mem "numbers" (Jsont.array Jsont.int) ~enc:(fun d -> d.numbers)
-
|> Jsont.Object.mem "strings" (Jsont.array Jsont.string) ~enc:(fun d -> d.strings)
+
Jsont.Object.map ~kind:"Data" (fun numbers strings ->
+
{ numbers; strings })
+
|> Jsont.Object.mem "numbers" (Jsont.array Jsont.int) ~enc:(fun d ->
+
d.numbers)
+
|> Jsont.Object.mem "strings" (Jsont.array Jsont.string) ~enc:(fun d ->
+
d.strings)
|> Jsont.Object.finish
end in
-
-
let data = { M.numbers = [|1; 2; 3; 4; 5|]; strings = [|"hello"; "world"|] } in
+
let data =
+
{ M.numbers = [| 1; 2; 3; 4; 5 |]; strings = [| "hello"; "world" |] }
+
in
(* Encode to JSON *)
(match Jsont_bytesrw.encode_string M.data_codec data with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(* Encode to YAML Block *)
(match Yamlt.encode_string ~format:Yamlt.Block M.data_codec data with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
(* Encode to YAML Flow *)
-
(match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e
let () =
let usage = "Usage: test_arrays <command> [args...]" in
···
end;
match Sys.argv.(1) with
-
| "int" when Array.length Sys.argv = 3 ->
-
test_int_array Sys.argv.(2)
-
-
| "string" when Array.length Sys.argv = 3 ->
-
test_string_array Sys.argv.(2)
-
-
| "float" when Array.length Sys.argv = 3 ->
-
test_float_array Sys.argv.(2)
-
-
| "empty" when Array.length Sys.argv = 3 ->
-
test_empty_array Sys.argv.(2)
-
-
| "objects" when Array.length Sys.argv = 3 ->
-
test_object_array Sys.argv.(2)
-
-
| "nested" when Array.length Sys.argv = 3 ->
-
test_nested_arrays Sys.argv.(2)
-
+
| "int" when Array.length Sys.argv = 3 -> test_int_array Sys.argv.(2)
+
| "string" when Array.length Sys.argv = 3 -> test_string_array Sys.argv.(2)
+
| "float" when Array.length Sys.argv = 3 -> test_float_array Sys.argv.(2)
+
| "empty" when Array.length Sys.argv = 3 -> test_empty_array Sys.argv.(2)
+
| "objects" when Array.length Sys.argv = 3 -> test_object_array Sys.argv.(2)
+
| "nested" when Array.length Sys.argv = 3 -> test_nested_arrays Sys.argv.(2)
| "type-mismatch" when Array.length Sys.argv = 3 ->
test_type_mismatch Sys.argv.(2)
-
-
| "bool" when Array.length Sys.argv = 3 ->
-
test_bool_array Sys.argv.(2)
-
+
| "bool" when Array.length Sys.argv = 3 -> test_bool_array Sys.argv.(2)
| "nullable" when Array.length Sys.argv = 3 ->
test_nullable_array Sys.argv.(2)
-
-
| "encode" when Array.length Sys.argv = 2 ->
-
test_encode_arrays ()
-
+
| "encode" when Array.length Sys.argv = 2 -> test_encode_arrays ()
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
+52 -41
tests/bin/test_complex.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test complex nested types with Yamlt *)
···
(* Test: Deeply nested objects *)
let test_deep_nesting file =
let module M = struct
-
type level3 = { value: int }
-
type level2 = { data: level3 }
-
type level1 = { nested: level2 }
-
type root = { top: level1 }
+
type level3 = { value : int }
+
type level2 = { data : level3 }
+
type level1 = { nested : level2 }
+
type root = { top : level1 }
let level3_codec =
Jsont.Object.map ~kind:"Level3" (fun value -> { value })
···
let show r = Printf.sprintf "depth=4, value=%d" r.top.nested.data.value
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.root_codec json in
···
(* Test: Array of objects with nested arrays *)
let test_mixed_structure file =
let module M = struct
-
type item = { id: int; tags: string array }
-
type collection = { name: string; items: item array }
+
type item = { id : int; tags : string array }
+
type collection = { name : string; items : item array }
let item_codec =
Jsont.Object.map ~kind:"Item" (fun id tags -> { id; tags })
|> Jsont.Object.mem "id" Jsont.int ~enc:(fun i -> i.id)
-
|> Jsont.Object.mem "tags" (Jsont.array Jsont.string) ~enc:(fun i -> i.tags)
+
|> Jsont.Object.mem "tags" (Jsont.array Jsont.string) ~enc:(fun i ->
+
i.tags)
|> Jsont.Object.finish
let collection_codec =
Jsont.Object.map ~kind:"Collection" (fun name items -> { name; items })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
-
|> Jsont.Object.mem "items" (Jsont.array item_codec) ~enc:(fun c -> c.items)
+
|> Jsont.Object.mem "items" (Jsont.array item_codec) ~enc:(fun c ->
+
c.items)
|> Jsont.Object.finish
let show c =
-
let total_tags = Stdlib.Array.fold_left (fun acc item ->
-
acc + Stdlib.Array.length item.tags) 0 c.items in
-
Printf.sprintf "name=%S, items=%d, total_tags=%d"
-
c.name (Stdlib.Array.length c.items) total_tags
+
let total_tags =
+
Stdlib.Array.fold_left
+
(fun acc item -> acc + Stdlib.Array.length item.tags)
+
0 c.items
+
in
+
Printf.sprintf "name=%S, items=%d, total_tags=%d" c.name
+
(Stdlib.Array.length c.items)
+
total_tags
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.collection_codec json in
···
let test_complex_optional file =
let module M = struct
type config = {
-
host: string;
-
port: int option;
-
ssl: bool option;
-
cert_path: string option;
-
fallback_hosts: string array option;
+
host : string;
+
port : int option;
+
ssl : bool option;
+
cert_path : string option;
+
fallback_hosts : string array option;
}
let config_codec =
···
|> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
|> Jsont.Object.opt_mem "port" Jsont.int ~enc:(fun c -> c.port)
|> Jsont.Object.opt_mem "ssl" Jsont.bool ~enc:(fun c -> c.ssl)
-
|> Jsont.Object.opt_mem "cert_path" Jsont.string ~enc:(fun c -> c.cert_path)
+
|> Jsont.Object.opt_mem "cert_path" Jsont.string ~enc:(fun c ->
+
c.cert_path)
|> Jsont.Object.opt_mem "fallback_hosts" (Jsont.array Jsont.string)
-
~enc:(fun c -> c.fallback_hosts)
+
~enc:(fun c -> c.fallback_hosts)
|> Jsont.Object.finish
let show c =
-
let port_str = match c.port with None -> "None" | Some p -> string_of_int p in
-
let ssl_str = match c.ssl with None -> "None" | Some b -> string_of_bool b in
-
let fallbacks = match c.fallback_hosts with
+
let port_str =
+
match c.port with None -> "None" | Some p -> string_of_int p
+
in
+
let ssl_str =
+
match c.ssl with None -> "None" | Some b -> string_of_bool b
+
in
+
let fallbacks =
+
match c.fallback_hosts with
| None -> 0
-
| Some arr -> Stdlib.Array.length arr in
-
Printf.sprintf "host=%S, port=%s, ssl=%s, fallbacks=%d"
-
c.host port_str ssl_str fallbacks
+
| Some arr -> Stdlib.Array.length arr
+
in
+
Printf.sprintf "host=%S, port=%s, ssl=%s, fallbacks=%d" c.host port_str
+
ssl_str fallbacks
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.config_codec json in
···
(* Test: Heterogeneous data via any type *)
let test_heterogeneous file =
let module M = struct
-
type data = { mixed: Jsont.json array }
+
type data = { mixed : Jsont.json array }
let data_codec =
Jsont.Object.map ~kind:"Data" (fun mixed -> { mixed })
-
|> Jsont.Object.mem "mixed" (Jsont.array (Jsont.any ())) ~enc:(fun d -> d.mixed)
+
|> Jsont.Object.mem "mixed"
+
(Jsont.array (Jsont.any ()))
+
~enc:(fun d -> d.mixed)
|> Jsont.Object.finish
let show d = Printf.sprintf "items=%d" (Stdlib.Array.length d.mixed)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.data_codec json in
···
match Sys.argv.(1) with
| "deep-nesting" when Stdlib.Array.length Sys.argv = 3 ->
test_deep_nesting Sys.argv.(2)
-
| "mixed-structure" when Stdlib.Array.length Sys.argv = 3 ->
test_mixed_structure Sys.argv.(2)
-
| "complex-optional" when Stdlib.Array.length Sys.argv = 3 ->
test_complex_optional Sys.argv.(2)
-
| "heterogeneous" when Stdlib.Array.length Sys.argv = 3 ->
test_heterogeneous Sys.argv.(2)
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
prerr_endline " deep-nesting <file> - Test deeply nested objects";
-
prerr_endline " mixed-structure <file> - Test arrays of objects with nested arrays";
-
prerr_endline " complex-optional <file> - Test complex optional/nullable combinations";
-
prerr_endline " heterogeneous <file> - Test heterogeneous data via any type";
+
prerr_endline
+
" mixed-structure <file> - Test arrays of objects with nested arrays";
+
prerr_endline
+
" complex-optional <file> - Test complex optional/nullable \
+
combinations";
+
prerr_endline
+
" heterogeneous <file> - Test heterogeneous data via any type";
exit 1
+48 -44
tests/bin/test_comprehensive.ml
···
|> 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");
-
+
| 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");
-
+
| 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");
-
+
| 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");
-
+
| 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");
-
+
| 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.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");
-
+
| 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");
-
+
| 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");
-
+
| 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.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");
-
+
| 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");
-
+
| 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.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)
+
+
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
+32 -32
tests/bin/test_edge.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test edge cases with Yamlt *)
···
(* Test: Very large numbers *)
let test_large_numbers file =
let module M = struct
-
type numbers = { large_int: float; large_float: float; small_float: float }
+
type numbers = {
+
large_int : float;
+
large_float : float;
+
small_float : float;
+
}
let numbers_codec =
Jsont.Object.map ~kind:"Numbers" (fun large_int large_float small_float ->
-
{ large_int; large_float; small_float })
+
{ large_int; large_float; small_float })
|> Jsont.Object.mem "large_int" Jsont.number ~enc:(fun n -> n.large_int)
-
|> Jsont.Object.mem "large_float" Jsont.number ~enc:(fun n -> n.large_float)
-
|> Jsont.Object.mem "small_float" Jsont.number ~enc:(fun n -> n.small_float)
+
|> Jsont.Object.mem "large_float" Jsont.number ~enc:(fun n ->
+
n.large_float)
+
|> Jsont.Object.mem "small_float" Jsont.number ~enc:(fun n ->
+
n.small_float)
|> Jsont.Object.finish
let show n =
Printf.sprintf "large_int=%.0f, large_float=%e, small_float=%e"
n.large_int n.large_float n.small_float
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
···
(* Test: Special characters in strings *)
let test_special_chars file =
let module M = struct
-
type text = { content: string }
+
type text = { content : string }
let text_codec =
Jsont.Object.map ~kind:"Text" (fun content -> { content })
···
(String.contains t.content '\n')
(String.contains t.content '\t')
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.text_codec json in
···
(* Test: Unicode strings *)
let test_unicode file =
let module M = struct
-
type text = { emoji: string; chinese: string; rtl: string }
+
type text = { emoji : string; chinese : string; rtl : string }
let text_codec =
-
Jsont.Object.map ~kind:"Text" (fun emoji chinese rtl -> { emoji; chinese; rtl })
+
Jsont.Object.map ~kind:"Text" (fun emoji chinese rtl ->
+
{ emoji; chinese; rtl })
|> Jsont.Object.mem "emoji" Jsont.string ~enc:(fun t -> t.emoji)
|> Jsont.Object.mem "chinese" Jsont.string ~enc:(fun t -> t.chinese)
|> Jsont.Object.mem "rtl" Jsont.string ~enc:(fun t -> t.rtl)
···
let show t =
Printf.sprintf "emoji=%S, chinese=%S, rtl=%S" t.emoji t.chinese t.rtl
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.text_codec json in
···
(* Test: Empty collections *)
let test_empty_collections file =
let module M = struct
-
type data = { empty_array: int array; empty_object_array: unit array }
+
type data = { empty_array : int array; empty_object_array : unit array }
let data_codec =
Jsont.Object.map ~kind:"Data" (fun empty_array empty_object_array ->
-
{ empty_array; empty_object_array })
-
|> Jsont.Object.mem "empty_array" (Jsont.array Jsont.int) ~enc:(fun d -> d.empty_array)
-
|> Jsont.Object.mem "empty_object_array" (Jsont.array (Jsont.null ())) ~enc:(fun d -> d.empty_object_array)
+
{ empty_array; empty_object_array })
+
|> Jsont.Object.mem "empty_array" (Jsont.array Jsont.int) ~enc:(fun d ->
+
d.empty_array)
+
|> Jsont.Object.mem "empty_object_array"
+
(Jsont.array (Jsont.null ()))
+
~enc:(fun d -> d.empty_object_array)
|> Jsont.Object.finish
let show d =
···
(Stdlib.Array.length d.empty_array)
(Stdlib.Array.length d.empty_object_array)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.data_codec json in
···
| Ok _ -> "not_object"
| Error _ -> "decode_error"
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string (Jsont.any ()) json in
···
(* Test: Single-element arrays *)
let test_single_element file =
let module M = struct
-
type data = { single: int array }
+
type data = { single : int array }
let data_codec =
Jsont.Object.map ~kind:"Data" (fun single -> { single })
-
|> Jsont.Object.mem "single" (Jsont.array Jsont.int) ~enc:(fun d -> d.single)
+
|> Jsont.Object.mem "single" (Jsont.array Jsont.int) ~enc:(fun d ->
+
d.single)
|> Jsont.Object.finish
let show d =
···
(Stdlib.Array.length d.single)
(if Stdlib.Array.length d.single > 0 then d.single.(0) else 0)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.data_codec json in
···
match Sys.argv.(1) with
| "large-numbers" when Stdlib.Array.length Sys.argv = 3 ->
test_large_numbers Sys.argv.(2)
-
| "special-chars" when Stdlib.Array.length Sys.argv = 3 ->
test_special_chars Sys.argv.(2)
-
-
| "unicode" when Stdlib.Array.length Sys.argv = 3 ->
-
test_unicode Sys.argv.(2)
-
+
| "unicode" when Stdlib.Array.length Sys.argv = 3 -> test_unicode Sys.argv.(2)
| "empty-collections" when Stdlib.Array.length Sys.argv = 3 ->
test_empty_collections Sys.argv.(2)
-
| "special-keys" when Stdlib.Array.length Sys.argv = 3 ->
test_special_keys Sys.argv.(2)
-
| "single-element" when Stdlib.Array.length Sys.argv = 3 ->
test_single_element Sys.argv.(2)
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
prerr_endline " large-numbers <file> - Test very large numbers";
-
prerr_endline " special-chars <file> - Test special characters in strings";
+
prerr_endline
+
" special-chars <file> - Test special characters in strings";
prerr_endline " unicode <file> - Test Unicode strings";
prerr_endline " empty-collections <file> - Test empty collections";
-
prerr_endline " special-keys <file> - Test special characters in keys";
+
prerr_endline
+
" special-keys <file> - Test special characters in keys";
prerr_endline " single-element <file> - Test single-element arrays";
exit 1
+4 -2
tests/bin/test_flow_newline.ml
···
|> 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
+
+
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;
+40 -52
tests/bin/test_formats.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test format-specific features with Yamlt *)
···
(* Test: Multi-line strings - literal style *)
let test_literal_string file =
let module M = struct
-
type text = { content: string }
+
type text = { content : string }
let text_codec =
Jsont.Object.map ~kind:"Text" (fun content -> { content })
···
(List.length (String.split_on_char '\n' t.content))
(String.length t.content)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.text_codec json in
···
(* Test: Multi-line strings - folded style *)
let test_folded_string file =
let module M = struct
-
type text = { content: string }
+
type text = { content : string }
let text_codec =
Jsont.Object.map ~kind:"Text" (fun content -> { content })
···
|> Jsont.Object.finish
let show t =
-
Printf.sprintf "length=%d, newlines=%d"
-
(String.length t.content)
-
(List.length (List.filter (fun c -> c = '\n')
-
(List.init (String.length t.content) (String.get t.content))))
+
Printf.sprintf "length=%d, newlines=%d" (String.length t.content)
+
(List.length
+
(List.filter
+
(fun c -> c = '\n')
+
(List.init (String.length t.content) (String.get t.content))))
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.text_codec json in
···
(* Test: Number formats - hex, octal, binary *)
let test_number_formats file =
let module M = struct
-
type numbers = { hex: float; octal: float; binary: float }
+
type numbers = { hex : float; octal : float; binary : float }
let numbers_codec =
-
Jsont.Object.map ~kind:"Numbers" (fun hex octal binary -> { hex; octal; binary })
+
Jsont.Object.map ~kind:"Numbers" (fun hex octal binary ->
+
{ hex; octal; binary })
|> Jsont.Object.mem "hex" Jsont.number ~enc:(fun n -> n.hex)
|> Jsont.Object.mem "octal" Jsont.number ~enc:(fun n -> n.octal)
|> Jsont.Object.mem "binary" Jsont.number ~enc:(fun n -> n.binary)
···
let show n =
Printf.sprintf "hex=%.0f, octal=%.0f, binary=%.0f" n.hex n.octal n.binary
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
···
(* Test: Block vs Flow style encoding *)
let test_encode_styles () =
let module M = struct
-
type data = {
-
name: string;
-
values: int array;
-
nested: nested_data;
-
}
-
and nested_data = {
-
enabled: bool;
-
count: int;
-
}
+
type data = { name : string; values : int array; nested : nested_data }
+
and nested_data = { enabled : bool; count : int }
let nested_codec =
Jsont.Object.map ~kind:"Nested" (fun enabled count -> { enabled; count })
···
|> Jsont.Object.finish
let data_codec =
-
Jsont.Object.map ~kind:"Data" (fun name values nested -> { name; values; nested })
+
Jsont.Object.map ~kind:"Data" (fun name values nested ->
+
{ name; values; nested })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun d -> d.name)
-
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun d -> d.values)
+
|> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun d ->
+
d.values)
|> Jsont.Object.mem "nested" nested_codec ~enc:(fun d -> d.nested)
|> Jsont.Object.finish
end in
-
-
let data = {
-
M.name = "test";
-
values = [|1; 2; 3|];
-
nested = { enabled = true; count = 5 };
-
} in
+
let data =
+
{
+
M.name = "test";
+
values = [| 1; 2; 3 |];
+
nested = { enabled = true; count = 5 };
+
}
+
in
(* Encode to YAML Block style *)
(match Yamlt.encode_string ~format:Yamlt.Block M.data_codec data with
-
| Ok s -> Printf.printf "YAML Block:\n%s\n" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
| Ok s -> Printf.printf "YAML Block:\n%s\n" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
(* Encode to YAML Flow style *)
-
(match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
-
| Ok s -> Printf.printf "YAML Flow:\n%s\n" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
+
| Ok s -> Printf.printf "YAML Flow:\n%s\n" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e
(* Test: Comments in YAML (should be ignored) *)
let test_comments file =
let module M = struct
-
type config = { host: string; port: int; debug: bool }
+
type config = { host : string; port : int; debug : bool }
let config_codec =
-
Jsont.Object.map ~kind:"Config" (fun host port debug -> { host; port; debug })
+
Jsont.Object.map ~kind:"Config" (fun host port debug ->
+
{ host; port; debug })
|> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
|> Jsont.Object.mem "port" Jsont.int ~enc:(fun c -> c.port)
|> Jsont.Object.mem "debug" Jsont.bool ~enc:(fun c -> c.debug)
···
let show c =
Printf.sprintf "host=%S, port=%d, debug=%b" c.host c.port c.debug
end in
-
let yaml = read_file file in
let yaml_result = Yamlt.decode_string M.config_codec yaml in
···
(* Test: Empty documents and null documents *)
let test_empty_document file =
let module M = struct
-
type wrapper = { value: string option }
+
type wrapper = { value : string option }
let wrapper_codec =
Jsont.Object.map ~kind:"Wrapper" (fun value -> { value })
-
|> Jsont.Object.mem "value" (Jsont.some Jsont.string) ~enc:(fun w -> w.value)
+
|> Jsont.Object.mem "value" (Jsont.some Jsont.string) ~enc:(fun w ->
+
w.value)
|> Jsont.Object.finish
let show w =
···
| None -> "value=None"
| Some s -> Printf.sprintf "value=Some(%S)" s
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.wrapper_codec json in
···
(* Test: Explicit typing with tags (if supported) *)
let test_explicit_tags file =
let module M = struct
-
type value_holder = { data: string }
+
type value_holder = { data : string }
let value_codec =
Jsont.Object.map ~kind:"ValueHolder" (fun data -> { data })
···
let show v = Printf.sprintf "data=%S" v.data
end in
-
let yaml = read_file file in
let yaml_result = Yamlt.decode_string M.value_codec yaml in
···
match Sys.argv.(1) with
| "literal" when Stdlib.Array.length Sys.argv = 3 ->
test_literal_string Sys.argv.(2)
-
| "folded" when Stdlib.Array.length Sys.argv = 3 ->
test_folded_string Sys.argv.(2)
-
| "number-formats" when Stdlib.Array.length Sys.argv = 3 ->
test_number_formats Sys.argv.(2)
-
| "encode-styles" when Stdlib.Array.length Sys.argv = 2 ->
test_encode_styles ()
-
| "comments" when Stdlib.Array.length Sys.argv = 3 ->
test_comments Sys.argv.(2)
-
| "empty-doc" when Stdlib.Array.length Sys.argv = 3 ->
test_empty_document Sys.argv.(2)
-
| "explicit-tags" when Stdlib.Array.length Sys.argv = 3 ->
test_explicit_tags Sys.argv.(2)
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
prerr_endline " literal <file> - Test literal multi-line strings";
prerr_endline " folded <file> - Test folded multi-line strings";
-
prerr_endline " number-formats <file> - Test hex/octal/binary number formats";
+
prerr_endline
+
" number-formats <file> - Test hex/octal/binary number formats";
prerr_endline " encode-styles - Test block vs flow encoding";
prerr_endline " comments <file> - Test YAML with comments";
prerr_endline " empty-doc <file> - Test empty documents";
+65 -61
tests/bin/test_locations.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test location and layout preservation options with Yamlt codec *)
···
(* Nested object codec *)
let address_codec =
-
Jsont.Object.map ~kind:"Address" (fun street city zip -> (street, city, zip))
-
|> Jsont.Object.mem "street" Jsont.string ~enc:(fun (s,_,_) -> s)
-
|> Jsont.Object.mem "city" Jsont.string ~enc:(fun (_,c,_) -> c)
-
|> Jsont.Object.mem "zip" Jsont.int ~enc:(fun (_,_,z) -> z)
+
Jsont.Object.map ~kind:"Address" (fun street city zip ->
+
(street, city, zip))
+
|> Jsont.Object.mem "street" Jsont.string ~enc:(fun (s, _, _) -> s)
+
|> Jsont.Object.mem "city" Jsont.string ~enc:(fun (_, c, _) -> c)
+
|> Jsont.Object.mem "zip" Jsont.int ~enc:(fun (_, _, z) -> z)
|> Jsont.Object.finish
in
···
Printf.printf "=== Without layout (default) ===\n";
(match Yamlt.decode_string ~layout:false codec yaml with
-
| Ok (host, port) ->
-
Printf.printf "Decoded: host=%s, port=%d\n" host port;
-
Printf.printf "Meta preserved: no\n"
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok (host, port) ->
+
Printf.printf "Decoded: host=%s, port=%d\n" host port;
+
Printf.printf "Meta preserved: no\n"
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== With layout=true ===\n";
-
(match Yamlt.decode_string ~layout:true codec yaml with
-
| Ok (host, port) ->
-
Printf.printf "Decoded: host=%s, port=%d\n" host port;
-
Printf.printf "Meta preserved: yes (style info available for round-tripping)\n"
-
| Error e -> Printf.printf "Error: %s\n" e)
+
match Yamlt.decode_string ~layout:true codec yaml with
+
| Ok (host, port) ->
+
Printf.printf "Decoded: host=%s, port=%d\n" host port;
+
Printf.printf
+
"Meta preserved: yes (style info available for round-tripping)\n"
+
| Error e -> Printf.printf "Error: %s\n" e
(* Test: Round-trip with layout preservation *)
let test_roundtrip_layout file =
···
Printf.printf "\n=== Decode without layout, re-encode ===\n";
(match Yamlt.decode_string ~layout:false codec yaml with
-
| Ok items ->
-
(match Yamlt.encode_string ~format:Yamlt.Block codec items with
-
| Ok yaml_out -> Printf.printf "%s" yaml_out
-
| Error e -> Printf.printf "Encode error: %s\n" e)
-
| Error e -> Printf.printf "Decode error: %s\n" e);
+
| Ok items -> (
+
match Yamlt.encode_string ~format:Yamlt.Block codec items with
+
| Ok yaml_out -> Printf.printf "%s" yaml_out
+
| Error e -> Printf.printf "Encode error: %s\n" e)
+
| Error e -> Printf.printf "Decode error: %s\n" e);
-
Printf.printf "\n=== Decode with layout=true, re-encode with Layout format ===\n";
-
(match Yamlt.decode_string ~layout:true codec yaml with
-
| Ok items ->
-
(match Yamlt.encode_string ~format:Yamlt.Layout codec items with
-
| Ok yaml_out -> Printf.printf "%s" yaml_out
-
| Error e -> Printf.printf "Encode error: %s\n" e)
-
| Error e -> Printf.printf "Decode error: %s\n" e)
+
Printf.printf
+
"\n=== Decode with layout=true, re-encode with Layout format ===\n";
+
match Yamlt.decode_string ~layout:true codec yaml with
+
| Ok items -> (
+
match Yamlt.encode_string ~format:Yamlt.Layout codec items with
+
| Ok yaml_out -> Printf.printf "%s" yaml_out
+
| Error e -> Printf.printf "Encode error: %s\n" e)
+
| Error e -> Printf.printf "Decode error: %s\n" e
(* Test: File path in error messages *)
let test_file_path () =
···
let codec =
Jsont.Object.map ~kind:"Complete" (fun a b c -> (a, b, c))
-
|> Jsont.Object.mem "field_a" Jsont.string ~enc:(fun (a,_,_) -> a)
-
|> Jsont.Object.mem "field_b" Jsont.int ~enc:(fun (_,b,_) -> b)
-
|> Jsont.Object.mem "field_c" Jsont.bool ~enc:(fun (_,_,c) -> c)
+
|> Jsont.Object.mem "field_a" Jsont.string ~enc:(fun (a, _, _) -> a)
+
|> Jsont.Object.mem "field_b" Jsont.int ~enc:(fun (_, b, _) -> b)
+
|> Jsont.Object.mem "field_c" Jsont.bool ~enc:(fun (_, _, c) -> c)
|> Jsont.Object.finish
in
···
let yaml = read_file file in
let codec =
-
Jsont.Object.map ~kind:"Settings" (fun timeout retries -> (timeout, retries))
+
Jsont.Object.map ~kind:"Settings" (fun timeout retries ->
+
(timeout, retries))
|> Jsont.Object.mem "timeout" Jsont.int ~enc:fst
|> Jsont.Object.mem "retries" Jsont.int ~enc:snd
|> Jsont.Object.finish
···
Printf.printf "=== locs=false, layout=false (defaults) ===\n";
(match Yamlt.decode_string ~locs:false ~layout:false codec yaml with
-
| Ok (timeout, retries) ->
-
Printf.printf "OK: timeout=%d, retries=%d\n" timeout retries
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok (timeout, retries) ->
+
Printf.printf "OK: timeout=%d, retries=%d\n" timeout retries
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== locs=true, layout=false ===\n";
(match Yamlt.decode_string ~locs:true ~layout:false codec yaml with
-
| Ok (timeout, retries) ->
-
Printf.printf "OK: timeout=%d, retries=%d (with precise locations)\n" timeout retries
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok (timeout, retries) ->
+
Printf.printf "OK: timeout=%d, retries=%d (with precise locations)\n"
+
timeout retries
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== locs=false, layout=true ===\n";
(match Yamlt.decode_string ~locs:false ~layout:true codec yaml with
-
| Ok (timeout, retries) ->
-
Printf.printf "OK: timeout=%d, retries=%d (with layout metadata)\n" timeout retries
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok (timeout, retries) ->
+
Printf.printf "OK: timeout=%d, retries=%d (with layout metadata)\n"
+
timeout retries
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== locs=true, layout=true (both enabled) ===\n";
-
(match Yamlt.decode_string ~locs:true ~layout:true codec yaml with
-
| Ok (timeout, retries) ->
-
Printf.printf "OK: timeout=%d, retries=%d (with locations and layout)\n" timeout retries
-
| Error e -> Printf.printf "Error: %s\n" e)
+
match Yamlt.decode_string ~locs:true ~layout:true codec yaml with
+
| Ok (timeout, retries) ->
+
Printf.printf "OK: timeout=%d, retries=%d (with locations and layout)\n"
+
timeout retries
+
| Error e -> Printf.printf "Error: %s\n" e
let () =
let usage = "Usage: test_locations <command> [args...]" in
···
match Sys.argv.(1) with
| "error-precision" when Array.length Sys.argv = 3 ->
test_error_precision Sys.argv.(2)
-
| "nested-error" when Array.length Sys.argv = 3 ->
test_nested_error Sys.argv.(2)
-
| "array-error" when Array.length Sys.argv = 3 ->
test_array_error Sys.argv.(2)
-
| "layout" when Array.length Sys.argv = 3 ->
test_layout_preservation Sys.argv.(2)
-
| "roundtrip" when Array.length Sys.argv = 3 ->
test_roundtrip_layout Sys.argv.(2)
-
-
| "file-path" ->
-
test_file_path ()
-
+
| "file-path" -> test_file_path ()
| "missing-field" when Array.length Sys.argv = 3 ->
test_missing_field Sys.argv.(2)
-
| "combined" when Array.length Sys.argv = 3 ->
test_combined_options Sys.argv.(2)
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
-
prerr_endline " error-precision <file> - Compare error messages with/without locs";
-
prerr_endline " nested-error <file> - Test error locations in nested objects";
-
prerr_endline " array-error <file> - Test error locations in arrays";
+
prerr_endline
+
" error-precision <file> - Compare error messages with/without locs";
+
prerr_endline
+
" nested-error <file> - Test error locations in nested objects";
+
prerr_endline
+
" array-error <file> - Test error locations in arrays";
prerr_endline " layout <file> - Test layout preservation";
-
prerr_endline " roundtrip <file> - Test round-tripping with layout";
-
prerr_endline " file-path - Test file path in error messages";
-
prerr_endline " missing-field <file> - Test missing field errors with locs";
+
prerr_endline
+
" roundtrip <file> - Test round-tripping with layout";
+
prerr_endline
+
" file-path - Test file path in error messages";
+
prerr_endline
+
" missing-field <file> - Test missing field errors with locs";
prerr_endline " combined <file> - Test locs and layout together";
exit 1
+13 -9
tests/bin/test_null_complete.ml
···
|> Object.finish
in
(match Yamlt.decode_string codec1 yaml1 with
-
| Ok v -> Printf.printf "Result: %s\n" (match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok v ->
+
Printf.printf "Result: %s\n"
+
(match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== Test 2: Jsont.option with YAML string ===\n";
(match Yamlt.decode_string codec1 "value: hello" with
-
| Ok v -> Printf.printf "Result: %s\n" (match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok v ->
+
Printf.printf "Result: %s\n"
+
(match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
+
| Error e -> Printf.printf "Error: %s\n" e);
Printf.printf "\n=== Test 3: Jsont.string with YAML null (should error) ===\n";
let codec2 =
···
|> Object.finish
in
(match Yamlt.decode_string codec2 "value: null" with
-
| Ok v -> Printf.printf "Result: %s\n" v
-
| Error e -> Printf.printf "Error (expected): %s\n" e);
+
| Ok v -> Printf.printf "Result: %s\n" v
+
| Error e -> Printf.printf "Error (expected): %s\n" e);
Printf.printf "\n=== Test 4: Jsont.string with YAML string ===\n";
-
(match Yamlt.decode_string codec2 "value: hello" with
-
| Ok v -> Printf.printf "Result: %s\n" v
-
| Error e -> Printf.printf "Error: %s\n" e)
+
match Yamlt.decode_string codec2 "value: hello" with
+
| Ok v -> Printf.printf "Result: %s\n" v
+
| Error e -> Printf.printf "Error: %s\n" e
+17 -16
tests/bin/test_null_fix.ml
···
let () =
let module M = struct
-
type data = { value: string option }
-
+
type data = { value : string option }
+
let data_codec =
Jsont.Object.map ~kind:"Data" (fun value -> { value })
-
|> Jsont.Object.mem "value" (Jsont.option Jsont.string) ~enc:(fun d -> d.value)
+
|> Jsont.Object.mem "value" (Jsont.option Jsont.string) ~enc:(fun d ->
+
d.value)
|> Jsont.Object.finish
end in
-
let yaml_null = "value: null" in
Printf.printf "Testing YAML null handling with Jsont.option Jsont.string:\n\n";
match Yamlt.decode_string M.data_codec yaml_null with
-
| Ok data ->
-
(match data.M.value with
-
| None -> Printf.printf "YAML: value=None (CORRECT)\n"
-
| Some s -> Printf.printf "YAML: value=Some(%S) (BUG!)\n" s)
-
| Error e -> Printf.printf "YAML ERROR: %s\n" e;
+
| Ok data -> (
+
match data.M.value with
+
| None -> Printf.printf "YAML: value=None (CORRECT)\n"
+
| Some s -> Printf.printf "YAML: value=Some(%S) (BUG!)\n" s)
+
| Error e -> (
+
Printf.printf "YAML ERROR: %s\n" e;
-
let json_null = "{\"value\": null}" in
-
match Jsont_bytesrw.decode_string M.data_codec json_null with
-
| Ok data ->
-
(match data.M.value with
-
| None -> Printf.printf "JSON: value=None (CORRECT)\n"
-
| Some s -> Printf.printf "JSON: value=Some(%S) (BUG!)\n" s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e
+
let json_null = "{\"value\": null}" in
+
match Jsont_bytesrw.decode_string M.data_codec json_null with
+
| Ok data -> (
+
match data.M.value with
+
| None -> Printf.printf "JSON: value=None (CORRECT)\n"
+
| Some s -> Printf.printf "JSON: value=Some(%S) (BUG!)\n" s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e)
+54 -65
tests/bin/test_objects.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test object codec functionality with Yamlt *)
···
(* Test: Simple object with required fields *)
let test_simple_object file =
let module M = struct
-
type person = { name: string; age: int }
+
type person = { name : string; age : int }
let person_codec =
Jsont.Object.map ~kind:"Person" (fun name age -> { name; age })
···
let show p = Printf.sprintf "{name=%S; age=%d}" p.name p.age
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.person_codec json in
···
(* Test: Object with optional fields *)
let test_optional_fields file =
let module M = struct
-
type config = { host: string; port: int option; debug: bool option }
+
type config = { host : string; port : int option; debug : bool option }
let config_codec =
-
Jsont.Object.map ~kind:"Config"
-
(fun host port debug -> { host; port; debug })
+
Jsont.Object.map ~kind:"Config" (fun host port debug ->
+
{ host; port; debug })
|> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
|> Jsont.Object.opt_mem "port" Jsont.int ~enc:(fun c -> c.port)
|> Jsont.Object.opt_mem "debug" Jsont.bool ~enc:(fun c -> c.debug)
|> Jsont.Object.finish
let show c =
-
Printf.sprintf "{host=%S; port=%s; debug=%s}"
-
c.host
-
(match c.port with None -> "None" | Some p -> Printf.sprintf "Some %d" p)
-
(match c.debug with None -> "None" | Some b -> Printf.sprintf "Some %b" b)
+
Printf.sprintf "{host=%S; port=%s; debug=%s}" c.host
+
(match c.port with
+
| None -> "None"
+
| Some p -> Printf.sprintf "Some %d" p)
+
(match c.debug with
+
| None -> "None"
+
| Some b -> Printf.sprintf "Some %b" b)
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.config_codec json in
···
(* Test: Object with default values *)
let test_default_values file =
let module M = struct
-
type settings = { timeout: int; retries: int; verbose: bool }
+
type settings = { timeout : int; retries : int; verbose : bool }
let settings_codec =
-
Jsont.Object.map ~kind:"Settings"
-
(fun timeout retries verbose -> { timeout; retries; verbose })
-
|> Jsont.Object.mem "timeout" Jsont.int ~enc:(fun s -> s.timeout) ~dec_absent:30
-
|> Jsont.Object.mem "retries" Jsont.int ~enc:(fun s -> s.retries) ~dec_absent:3
-
|> Jsont.Object.mem "verbose" Jsont.bool ~enc:(fun s -> s.verbose) ~dec_absent:false
+
Jsont.Object.map ~kind:"Settings" (fun timeout retries verbose ->
+
{ timeout; retries; verbose })
+
|> Jsont.Object.mem "timeout" Jsont.int
+
~enc:(fun s -> s.timeout)
+
~dec_absent:30
+
|> Jsont.Object.mem "retries" Jsont.int
+
~enc:(fun s -> s.retries)
+
~dec_absent:3
+
|> Jsont.Object.mem "verbose" Jsont.bool
+
~enc:(fun s -> s.verbose)
+
~dec_absent:false
|> Jsont.Object.finish
let show s =
-
Printf.sprintf "{timeout=%d; retries=%d; verbose=%b}"
-
s.timeout s.retries s.verbose
+
Printf.sprintf "{timeout=%d; retries=%d; verbose=%b}" s.timeout s.retries
+
s.verbose
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.settings_codec json in
···
(* Test: Nested objects *)
let test_nested_objects file =
let module M = struct
-
type address = { street: string; city: string; zip: string }
-
type employee = { name: string; address: address }
+
type address = { street : string; city : string; zip : string }
+
type employee = { name : string; address : address }
let address_codec =
-
Jsont.Object.map ~kind:"Address"
-
(fun street city zip -> { street; city; zip })
+
Jsont.Object.map ~kind:"Address" (fun street city zip ->
+
{ street; city; zip })
|> Jsont.Object.mem "street" Jsont.string ~enc:(fun a -> a.street)
|> Jsont.Object.mem "city" Jsont.string ~enc:(fun a -> a.city)
|> Jsont.Object.mem "zip" Jsont.string ~enc:(fun a -> a.zip)
|> Jsont.Object.finish
let employee_codec =
-
Jsont.Object.map ~kind:"Employee"
-
(fun name address -> { name; address })
+
Jsont.Object.map ~kind:"Employee" (fun name address -> { name; address })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun e -> e.name)
|> Jsont.Object.mem "address" address_codec ~enc:(fun e -> e.address)
|> Jsont.Object.finish
let show e =
-
Printf.sprintf "{name=%S; address={street=%S; city=%S; zip=%S}}"
-
e.name e.address.street e.address.city e.address.zip
+
Printf.sprintf "{name=%S; address={street=%S; city=%S; zip=%S}}" e.name
+
e.address.street e.address.city e.address.zip
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.employee_codec json in
···
(* Test: Unknown member handling - error *)
let test_unknown_members_error file =
let module M = struct
-
type strict = { name: string }
+
type strict = { name : string }
let strict_codec =
Jsont.Object.map ~kind:"Strict" (fun name -> { name })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun s -> s.name)
|> Jsont.Object.finish
end in
-
let yaml = read_file file in
let result = Yamlt.decode_string M.strict_codec yaml in
match result with
···
(* Test: Unknown member handling - keep *)
let test_unknown_members_keep file =
let module M = struct
-
type flexible = { name: string; extra: Jsont.json }
+
type flexible = { name : string; extra : Jsont.json }
let flexible_codec =
Jsont.Object.map ~kind:"Flexible" (fun name extra -> { name; extra })
···
|> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun f -> f.extra)
|> Jsont.Object.finish
-
let show f =
-
Printf.sprintf "{name=%S; has_extra=true}" f.name
+
let show f = Printf.sprintf "{name=%S; has_extra=true}" f.name
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.flexible_codec json in
···
(* Test: Object cases (discriminated unions) - simplified version *)
let test_object_cases file =
let module M = struct
-
type circle = { type_: string; radius: float }
+
type circle = { type_ : string; radius : float }
let circle_codec =
Jsont.Object.map ~kind:"Circle" (fun type_ radius -> { type_; radius })
···
|> Jsont.Object.mem "radius" Jsont.number ~enc:(fun c -> c.radius)
|> Jsont.Object.finish
-
let show c =
-
Printf.sprintf "Circle{radius=%.2f}" c.radius
+
let show c = Printf.sprintf "Circle{radius=%.2f}" c.radius
end in
-
let yaml = read_file file in
let json = read_file (file ^ ".json") in
let json_result = Jsont_bytesrw.decode_string M.circle_codec json in
···
(* Test: Missing required field error *)
let test_missing_required file =
let module M = struct
-
type required = { name: string; age: int }
+
type required = { name : string; age : int }
let required_codec =
Jsont.Object.map ~kind:"Required" (fun name age -> { name; age })
···
|> Jsont.Object.mem "age" Jsont.int ~enc:(fun r -> r.age)
|> Jsont.Object.finish
end in
-
let yaml = read_file file in
let result = Yamlt.decode_string M.required_codec yaml in
match result with
···
(* Test: Encoding objects to different formats *)
let test_encode_object () =
let module M = struct
-
type person = { name: string; age: int; active: bool }
+
type person = { name : string; age : int; active : bool }
let person_codec =
-
Jsont.Object.map ~kind:"Person" (fun name age active -> { name; age; active })
+
Jsont.Object.map ~kind:"Person" (fun name age active ->
+
{ name; age; active })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
|> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
|> Jsont.Object.mem "active" Jsont.bool ~enc:(fun p -> p.active)
|> Jsont.Object.finish
end in
-
let person = M.{ name = "Alice"; age = 30; active = true } in
(* Encode to JSON *)
(match Jsont_bytesrw.encode_string M.person_codec person with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(* Encode to YAML Block *)
(match Yamlt.encode_string ~format:Yamlt.Block M.person_codec person with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
(* Encode to YAML Flow *)
-
(match Yamlt.encode_string ~format:Yamlt.Flow M.person_codec person with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
match Yamlt.encode_string ~format:Yamlt.Flow M.person_codec person with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e
let () =
let usage = "Usage: test_objects <command> [args...]" in
···
match Sys.argv.(1) with
| "simple" when Stdlib.Array.length Sys.argv = 3 ->
test_simple_object Sys.argv.(2)
-
| "optional" when Stdlib.Array.length Sys.argv = 3 ->
test_optional_fields Sys.argv.(2)
-
| "defaults" when Stdlib.Array.length Sys.argv = 3 ->
test_default_values Sys.argv.(2)
-
| "nested" when Stdlib.Array.length Sys.argv = 3 ->
test_nested_objects Sys.argv.(2)
-
| "unknown-error" when Stdlib.Array.length Sys.argv = 3 ->
test_unknown_members_error Sys.argv.(2)
-
| "unknown-keep" when Stdlib.Array.length Sys.argv = 3 ->
test_unknown_members_keep Sys.argv.(2)
-
| "cases" when Stdlib.Array.length Sys.argv = 3 ->
test_object_cases Sys.argv.(2)
-
| "missing-required" when Stdlib.Array.length Sys.argv = 3 ->
test_missing_required Sys.argv.(2)
-
-
| "encode" when Stdlib.Array.length Sys.argv = 2 ->
-
test_encode_object ()
-
+
| "encode" when Stdlib.Array.length Sys.argv = 2 -> test_encode_object ()
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
···
prerr_endline " unknown-error <file> - Test unknown member error";
prerr_endline " unknown-keep <file> - Test keeping unknown members";
prerr_endline " cases <file> - Test object cases (unions)";
-
prerr_endline " missing-required <file> - Test missing required field error";
+
prerr_endline
+
" missing-required <file> - Test missing required field error";
prerr_endline " encode - Test encoding objects";
exit 1
+6 -5
tests/bin/test_opt_array.ml
···
let () =
let codec =
Jsont.Object.map ~kind:"Test" (fun arr -> arr)
-
|> Jsont.Object.opt_mem "values" (Jsont.array Jsont.string) ~enc:(fun arr -> arr)
+
|> Jsont.Object.opt_mem "values" (Jsont.array Jsont.string) ~enc:(fun arr ->
+
arr)
|> Jsont.Object.finish
in
···
Printf.printf "Testing optional array field:\n";
match Yamlt.decode_string codec yaml with
-
| Ok arr ->
-
(match arr with
-
| None -> Printf.printf "Result: None\n"
-
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
+
| Ok arr -> (
+
match arr with
+
| None -> Printf.printf "Result: None\n"
+
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
| Error e -> Printf.printf "Error: %s\n" e
+134 -88
tests/bin/test_roundtrip.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test roundtrip encoding/decoding with Yamlt *)
(* Test: Roundtrip scalars *)
let test_scalar_roundtrip () =
let module M = struct
-
type data = { s: string; n: float; b: bool; nul: unit }
+
type data = { s : string; n : float; b : bool; nul : unit }
let data_codec =
Jsont.Object.map ~kind:"Data" (fun s n b nul -> { s; n; b; nul })
···
let equal d1 d2 =
d1.s = d2.s && d1.n = d2.n && d1.b = d2.b && d1.nul = d2.nul
end in
-
let original = { M.s = "hello"; n = 42.5; b = true; nul = () } in
(* JSON roundtrip *)
let json_encoded = Jsont_bytesrw.encode_string M.data_codec original in
-
let json_decoded = Result.bind json_encoded (Jsont_bytesrw.decode_string M.data_codec) in
+
let json_decoded =
+
Result.bind json_encoded (Jsont_bytesrw.decode_string M.data_codec)
+
in
(match json_decoded with
-
| Ok decoded when M.equal original decoded -> Printf.printf "JSON roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "JSON roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "JSON roundtrip: FAIL (%s)\n" e);
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "JSON roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "JSON roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "JSON roundtrip: FAIL (%s)\n" e);
(* YAML Block roundtrip *)
-
let yaml_block_encoded = Yamlt.encode_string ~format:Yamlt.Block M.data_codec original in
-
let yaml_block_decoded = Result.bind yaml_block_encoded (Yamlt.decode_string M.data_codec) in
+
let yaml_block_encoded =
+
Yamlt.encode_string ~format:Yamlt.Block M.data_codec original
+
in
+
let yaml_block_decoded =
+
Result.bind yaml_block_encoded (Yamlt.decode_string M.data_codec)
+
in
(match yaml_block_decoded with
-
| Ok decoded when M.equal original decoded -> Printf.printf "YAML Block roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "YAML Block roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "YAML Block roundtrip: FAIL (%s)\n" e);
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "YAML Block roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "YAML Block roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "YAML Block roundtrip: FAIL (%s)\n" e);
(* YAML Flow roundtrip *)
-
let yaml_flow_encoded = Yamlt.encode_string ~format:Yamlt.Flow M.data_codec original in
-
let yaml_flow_decoded = Result.bind yaml_flow_encoded (Yamlt.decode_string M.data_codec) in
-
(match yaml_flow_decoded with
-
| Ok decoded when M.equal original decoded -> Printf.printf "YAML Flow roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "YAML Flow roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "YAML Flow roundtrip: FAIL (%s)\n" e)
+
let yaml_flow_encoded =
+
Yamlt.encode_string ~format:Yamlt.Flow M.data_codec original
+
in
+
let yaml_flow_decoded =
+
Result.bind yaml_flow_encoded (Yamlt.decode_string M.data_codec)
+
in
+
match yaml_flow_decoded with
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "YAML Flow roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "YAML Flow roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "YAML Flow roundtrip: FAIL (%s)\n" e
(* Test: Roundtrip arrays *)
let test_array_roundtrip () =
let module M = struct
-
type data = { items: int array; nested: float array array }
+
type data = { items : int array; nested : float array array }
let data_codec =
Jsont.Object.map ~kind:"Data" (fun items nested -> { items; nested })
-
|> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun d -> d.items)
-
|> Jsont.Object.mem "nested" (Jsont.array (Jsont.array Jsont.number)) ~enc:(fun d -> d.nested)
+
|> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun d ->
+
d.items)
+
|> Jsont.Object.mem "nested"
+
(Jsont.array (Jsont.array Jsont.number))
+
~enc:(fun d -> d.nested)
|> Jsont.Object.finish
-
let equal d1 d2 =
-
d1.items = d2.items && d1.nested = d2.nested
+
let equal d1 d2 = d1.items = d2.items && d1.nested = d2.nested
end in
-
-
let original = { M.items = [|1; 2; 3; 4; 5|]; nested = [|[|1.0; 2.0|]; [|3.0; 4.0|]|] } in
+
let original =
+
{
+
M.items = [| 1; 2; 3; 4; 5 |];
+
nested = [| [| 1.0; 2.0 |]; [| 3.0; 4.0 |] |];
+
}
+
in
(* JSON roundtrip *)
-
let json_result = Result.bind
-
(Jsont_bytesrw.encode_string M.data_codec original)
-
(Jsont_bytesrw.decode_string M.data_codec) in
+
let json_result =
+
Result.bind
+
(Jsont_bytesrw.encode_string M.data_codec original)
+
(Jsont_bytesrw.decode_string M.data_codec)
+
in
(match json_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "JSON array roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "JSON array roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "JSON array roundtrip: FAIL (%s)\n" e);
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "JSON array roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "JSON array roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "JSON array roundtrip: FAIL (%s)\n" e);
(* YAML roundtrip *)
-
let yaml_result = Result.bind
-
(Yamlt.encode_string M.data_codec original)
-
(Yamlt.decode_string M.data_codec) in
-
(match yaml_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "YAML array roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "YAML array roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "YAML array roundtrip: FAIL (%s)\n" e)
+
let yaml_result =
+
Result.bind
+
(Yamlt.encode_string M.data_codec original)
+
(Yamlt.decode_string M.data_codec)
+
in
+
match yaml_result with
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "YAML array roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "YAML array roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "YAML array roundtrip: FAIL (%s)\n" e
(* Test: Roundtrip objects *)
let test_object_roundtrip () =
let module M = struct
-
type person = { p_name: string; age: int; active: bool }
-
type company = { c_name: string; employees: person array }
+
type person = { p_name : string; age : int; active : bool }
+
type company = { c_name : string; employees : person array }
let person_codec =
-
Jsont.Object.map ~kind:"Person" (fun p_name age active -> { p_name; age; active })
+
Jsont.Object.map ~kind:"Person" (fun p_name age active ->
+
{ p_name; age; active })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.p_name)
|> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
|> Jsont.Object.mem "active" Jsont.bool ~enc:(fun p -> p.active)
|> Jsont.Object.finish
let company_codec =
-
Jsont.Object.map ~kind:"Company" (fun c_name employees -> { c_name; employees })
+
Jsont.Object.map ~kind:"Company" (fun c_name employees ->
+
{ c_name; employees })
|> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.c_name)
-
|> Jsont.Object.mem "employees" (Jsont.array person_codec) ~enc:(fun c -> c.employees)
+
|> Jsont.Object.mem "employees" (Jsont.array person_codec) ~enc:(fun c ->
+
c.employees)
|> Jsont.Object.finish
let person_equal p1 p2 =
p1.p_name = p2.p_name && p1.age = p2.age && p1.active = p2.active
let equal c1 c2 =
-
c1.c_name = c2.c_name &&
-
Stdlib.Array.length c1.employees = Stdlib.Array.length c2.employees &&
-
Stdlib.Array.for_all2 person_equal c1.employees c2.employees
+
c1.c_name = c2.c_name
+
&& Stdlib.Array.length c1.employees = Stdlib.Array.length c2.employees
+
&& Stdlib.Array.for_all2 person_equal c1.employees c2.employees
end in
-
-
let original = {
-
M.c_name = "Acme Corp";
-
employees = [|
-
{ p_name = "Alice"; age = 30; active = true };
-
{ p_name = "Bob"; age = 25; active = false };
-
|]
-
} in
+
let original =
+
{
+
M.c_name = "Acme Corp";
+
employees =
+
[|
+
{ p_name = "Alice"; age = 30; active = true };
+
{ p_name = "Bob"; age = 25; active = false };
+
|];
+
}
+
in
(* JSON roundtrip *)
-
let json_result = Result.bind
-
(Jsont_bytesrw.encode_string M.company_codec original)
-
(Jsont_bytesrw.decode_string M.company_codec) in
+
let json_result =
+
Result.bind
+
(Jsont_bytesrw.encode_string M.company_codec original)
+
(Jsont_bytesrw.decode_string M.company_codec)
+
in
(match json_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "JSON object roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "JSON object roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "JSON object roundtrip: FAIL (%s)\n" e);
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "JSON object roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "JSON object roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "JSON object roundtrip: FAIL (%s)\n" e);
(* YAML roundtrip *)
-
let yaml_result = Result.bind
-
(Yamlt.encode_string M.company_codec original)
-
(Yamlt.decode_string M.company_codec) in
-
(match yaml_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "YAML object roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "YAML object roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "YAML object roundtrip: FAIL (%s)\n" e)
+
let yaml_result =
+
Result.bind
+
(Yamlt.encode_string M.company_codec original)
+
(Yamlt.decode_string M.company_codec)
+
in
+
match yaml_result with
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "YAML object roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "YAML object roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "YAML object roundtrip: FAIL (%s)\n" e
(* Test: Roundtrip with optionals *)
let test_optional_roundtrip () =
let module M = struct
-
type data = { required: string; optional: int option; nullable: string option }
+
type data = {
+
required : string;
+
optional : int option;
+
nullable : string option;
+
}
let data_codec =
-
Jsont.Object.map ~kind:"Data" (fun required optional nullable -> { required; optional; nullable })
+
Jsont.Object.map ~kind:"Data" (fun required optional nullable ->
+
{ required; optional; nullable })
|> Jsont.Object.mem "required" Jsont.string ~enc:(fun d -> d.required)
|> Jsont.Object.opt_mem "optional" Jsont.int ~enc:(fun d -> d.optional)
-
|> Jsont.Object.mem "nullable" (Jsont.some Jsont.string) ~enc:(fun d -> d.nullable)
+
|> Jsont.Object.mem "nullable" (Jsont.some Jsont.string) ~enc:(fun d ->
+
d.nullable)
|> Jsont.Object.finish
let equal d1 d2 =
-
d1.required = d2.required && d1.optional = d2.optional && d1.nullable = d2.nullable
+
d1.required = d2.required && d1.optional = d2.optional
+
&& d1.nullable = d2.nullable
end in
-
let original = { M.required = "test"; optional = Some 42; nullable = None } in
(* JSON roundtrip *)
-
let json_result = Result.bind
-
(Jsont_bytesrw.encode_string M.data_codec original)
-
(Jsont_bytesrw.decode_string M.data_codec) in
+
let json_result =
+
Result.bind
+
(Jsont_bytesrw.encode_string M.data_codec original)
+
(Jsont_bytesrw.decode_string M.data_codec)
+
in
(match json_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "JSON optional roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "JSON optional roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "JSON optional roundtrip: FAIL (%s)\n" e);
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "JSON optional roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "JSON optional roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "JSON optional roundtrip: FAIL (%s)\n" e);
(* YAML roundtrip *)
-
let yaml_result = Result.bind
-
(Yamlt.encode_string M.data_codec original)
-
(Yamlt.decode_string M.data_codec) in
-
(match yaml_result with
-
| Ok decoded when M.equal original decoded -> Printf.printf "YAML optional roundtrip: PASS\n"
-
| Ok _ -> Printf.printf "YAML optional roundtrip: FAIL (data mismatch)\n"
-
| Error e -> Printf.printf "YAML optional roundtrip: FAIL (%s)\n" e)
+
let yaml_result =
+
Result.bind
+
(Yamlt.encode_string M.data_codec original)
+
(Yamlt.decode_string M.data_codec)
+
in
+
match yaml_result with
+
| Ok decoded when M.equal original decoded ->
+
Printf.printf "YAML optional roundtrip: PASS\n"
+
| Ok _ -> Printf.printf "YAML optional roundtrip: FAIL (data mismatch)\n"
+
| Error e -> Printf.printf "YAML optional roundtrip: FAIL (%s)\n" e
let () =
let usage = "Usage: test_roundtrip <command>" in
···
| "array" -> test_array_roundtrip ()
| "object" -> test_object_roundtrip ()
| "optional" -> test_optional_roundtrip ()
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
+80 -93
tests/bin/test_scalars.ml
···
(*---------------------------------------------------------------------------
-
Copyright (c) 2024 The yamlrw programmers. All rights reserved.
-
SPDX-License-Identifier: ISC
-
---------------------------------------------------------------------------*)
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
(** Test scalar type resolution with Yamlt codec *)
···
let result = Yamlt.decode_string number_codec yaml in
match result with
| Ok f ->
-
if Float.is_nan f then
-
Printf.printf "value: NaN\n"
-
else if f = Float.infinity then
-
Printf.printf "value: +Infinity\n"
-
else if f = Float.neg_infinity then
-
Printf.printf "value: -Infinity\n"
-
else
-
Printf.printf "value: %.17g\n" f
-
| Error e ->
-
Printf.printf "ERROR: %s\n" e
+
if Float.is_nan f then Printf.printf "value: NaN\n"
+
else if f = Float.infinity then Printf.printf "value: +Infinity\n"
+
else if f = Float.neg_infinity then Printf.printf "value: -Infinity\n"
+
else Printf.printf "value: %.17g\n" f
+
| Error e -> Printf.printf "ERROR: %s\n" e
(* Test: Type mismatch errors *)
let test_type_mismatch file expected_type =
let yaml = read_file file in
match expected_type with
-
| "bool" ->
-
let codec =
-
Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
-
|> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
-
|> Jsont.Object.finish
-
in
-
let result = Yamlt.decode_string codec yaml in
-
(match result with
-
| Ok _ -> Printf.printf "Unexpected success\n"
-
| Error e -> Printf.printf "Expected error: %s\n" e)
-
| "number" ->
-
let codec =
-
Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
-
|> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
-
|> Jsont.Object.finish
-
in
-
let result = Yamlt.decode_string codec yaml in
-
(match result with
-
| Ok _ -> Printf.printf "Unexpected success\n"
-
| Error e -> Printf.printf "Expected error: %s\n" e)
-
| "null" ->
-
let codec =
-
Jsont.Object.map ~kind:"NullTest" (fun n -> n)
-
|> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
-
|> Jsont.Object.finish
-
in
-
let result = Yamlt.decode_string codec yaml in
-
(match result with
-
| Ok _ -> Printf.printf "Unexpected success\n"
-
| Error e -> Printf.printf "Expected error: %s\n" e)
-
| _ -> failwith "unknown type"
+
| "bool" -> (
+
let codec =
+
Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
+
|> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
+
|> Jsont.Object.finish
+
in
+
let result = Yamlt.decode_string codec yaml in
+
match result with
+
| Ok _ -> Printf.printf "Unexpected success\n"
+
| Error e -> Printf.printf "Expected error: %s\n" e)
+
| "number" -> (
+
let codec =
+
Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
+
|> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
+
|> Jsont.Object.finish
+
in
+
let result = Yamlt.decode_string codec yaml in
+
match result with
+
| Ok _ -> Printf.printf "Unexpected success\n"
+
| Error e -> Printf.printf "Expected error: %s\n" e)
+
| "null" -> (
+
let codec =
+
Jsont.Object.map ~kind:"NullTest" (fun n -> n)
+
|> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
+
|> Jsont.Object.finish
+
in
+
let result = Yamlt.decode_string codec yaml in
+
match result with
+
| Ok _ -> Printf.printf "Unexpected success\n"
+
| Error e -> Printf.printf "Expected error: %s\n" e)
+
| _ -> failwith "unknown type"
(* Test: Decode with Jsont.json to see auto-resolution *)
let test_any_resolution file =
···
(* Test: Encoding to different formats *)
let test_encode_formats value_type value =
match value_type with
-
| "bool" ->
+
| "bool" -> (
let codec =
Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
|> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
···
in
let v = bool_of_string value in
(match Jsont_bytesrw.encode_string codec v with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(match Yamlt.encode_string ~format:Yamlt.Block codec v with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
-
(match Yamlt.encode_string ~format:Yamlt.Flow codec v with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
-
| "number" ->
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
match Yamlt.encode_string ~format:Yamlt.Flow codec v with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
| "number" -> (
let codec =
Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
|> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
···
in
let v = float_of_string value in
(match Jsont_bytesrw.encode_string codec v with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(match Yamlt.encode_string ~format:Yamlt.Block codec v with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
-
(match Yamlt.encode_string ~format:Yamlt.Flow codec v with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
-
| "string" ->
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
match Yamlt.encode_string ~format:Yamlt.Flow codec v with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
| "string" -> (
let codec =
Jsont.Object.map ~kind:"StringTest" (fun s -> s)
|> Jsont.Object.mem "value" Jsont.string ~enc:(fun s -> s)
···
in
let v = value in
(match Jsont_bytesrw.encode_string codec v with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(match Yamlt.encode_string ~format:Yamlt.Block codec v with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
-
(match Yamlt.encode_string ~format:Yamlt.Flow codec v with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
-
| "null" ->
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
match Yamlt.encode_string ~format:Yamlt.Flow codec v with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
| "null" -> (
let codec =
Jsont.Object.map ~kind:"NullTest" (fun n -> n)
|> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
···
in
let v = () in
(match Jsont_bytesrw.encode_string codec v with
-
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
-
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
+
| Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
+
| Error e -> Printf.printf "JSON ERROR: %s\n" e);
(match Yamlt.encode_string ~format:Yamlt.Block codec v with
-
| Ok s -> Printf.printf "YAML Block:\n%s" s
-
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
-
(match Yamlt.encode_string ~format:Yamlt.Flow codec v with
-
| Ok s -> Printf.printf "YAML Flow: %s" s
-
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
+
| Ok s -> Printf.printf "YAML Block:\n%s" s
+
| Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
+
match Yamlt.encode_string ~format:Yamlt.Flow codec v with
+
| Ok s -> Printf.printf "YAML Flow: %s" s
+
| Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
| _ -> failwith "unknown type"
let () =
···
end;
match Sys.argv.(1) with
-
| "null" when Array.length Sys.argv = 3 ->
-
test_null_resolution Sys.argv.(2)
-
-
| "bool" when Array.length Sys.argv = 3 ->
-
test_bool_resolution Sys.argv.(2)
-
+
| "null" when Array.length Sys.argv = 3 -> test_null_resolution Sys.argv.(2)
+
| "bool" when Array.length Sys.argv = 3 -> test_bool_resolution Sys.argv.(2)
| "number" when Array.length Sys.argv = 3 ->
test_number_resolution Sys.argv.(2)
-
| "string" when Array.length Sys.argv = 3 ->
test_string_resolution Sys.argv.(2)
-
| "special-float" when Array.length Sys.argv = 3 ->
test_special_floats Sys.argv.(2)
-
| "type-mismatch" when Array.length Sys.argv = 4 ->
test_type_mismatch Sys.argv.(2) Sys.argv.(3)
-
-
| "any" when Array.length Sys.argv = 3 ->
-
test_any_resolution Sys.argv.(2)
-
+
| "any" when Array.length Sys.argv = 3 -> test_any_resolution Sys.argv.(2)
| "encode" when Array.length Sys.argv = 4 ->
test_encode_formats Sys.argv.(2) Sys.argv.(3)
-
| _ ->
prerr_endline usage;
prerr_endline "Commands:";
prerr_endline " null <file> - Test null resolution";
-
prerr_endline " bool <file> - Test bool vs string resolution";
+
prerr_endline
+
" bool <file> - Test bool vs string resolution";
prerr_endline " number <file> - Test number resolution";
prerr_endline " string <file> - Test string resolution";
prerr_endline " special-float <file> - Test .inf, .nan, etc.";
-
prerr_endline " type-mismatch <file> <type> - Test error on type mismatch";
-
prerr_endline " any <file> - Test Jsont.any auto-resolution";
+
prerr_endline
+
" type-mismatch <file> <type> - Test error on type mismatch";
+
prerr_endline
+
" any <file> - Test Jsont.any auto-resolution";
prerr_endline " encode <type> <value> - Test encoding to JSON/YAML";
exit 1
+17 -13
tests/bin/test_some_vs_option.ml
···
(* Using Jsont.some like opt_mem does *)
let codec1 =
Jsont.Object.map ~kind:"Test" (fun arr -> arr)
-
|> Jsont.Object.mem "values" (Jsont.some (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
+
|> Jsont.Object.mem "values"
+
(Jsont.some (Jsont.array Jsont.string))
+
~enc:(fun arr -> arr)
|> Jsont.Object.finish
in
···
Printf.printf "Test 1: Jsont.some (Jsont.array) - like opt_mem:\n";
(match Yamlt.decode_string codec1 yaml with
-
| Ok arr ->
-
(match arr with
-
| None -> Printf.printf "Result: None\n"
-
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
-
| Error e -> Printf.printf "Error: %s\n" e);
+
| Ok arr -> (
+
match arr with
+
| None -> Printf.printf "Result: None\n"
+
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
+
| Error e -> Printf.printf "Error: %s\n" e);
(* Using Jsont.option *)
let codec2 =
Jsont.Object.map ~kind:"Test" (fun arr -> arr)
-
|> Jsont.Object.mem "values" (Jsont.option (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
+
|> Jsont.Object.mem "values"
+
(Jsont.option (Jsont.array Jsont.string))
+
~enc:(fun arr -> arr)
|> Jsont.Object.finish
in
Printf.printf "\nTest 2: Jsont.option (Jsont.array):\n";
-
(match Yamlt.decode_string codec2 yaml with
-
| Ok arr ->
-
(match arr with
-
| None -> Printf.printf "Result: None\n"
-
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
-
| Error e -> Printf.printf "Error: %s\n" e)
+
match Yamlt.decode_string codec2 yaml with
+
| Ok arr -> (
+
match arr with
+
| None -> Printf.printf "Result: None\n"
+
| Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
+
| Error e -> Printf.printf "Error: %s\n" e
+5
yamlt.opam
···
synopsis: "YAML codec using Jsont type descriptions"
description:
"Allows the same Jsont.t codec definitions to work for both JSON and YAML"
+
maintainer: ["Anil Madhavapeddy <anil@recoil.org>"]
+
authors: ["Anil Madhavapeddy"]
+
license: "ISC"
+
homepage: "https://tangled.org/@anil.recoil.org/ocaml-yamlt"
+
bug-reports: "https://tangled.org/@anil.recoil.org/ocaml-yamlt/issues"
depends: [
"dune" {>= "3.18"}
"ocaml" {>= "4.14.0"}