···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+open Bytesrw
77+open Jsont.Repr
88+open Yamlrw
99+1010+(* YAML format *)
1111+1212+type yaml_format = Block | Flow | Layout
1313+1414+(* Decoder *)
1515+1616+type decoder = {
1717+ parser : Parser.t;
1818+ file : string;
1919+ locs : bool;
2020+ _layout : bool; (* For future layout preservation *)
2121+ max_depth : int;
2222+ max_nodes : int;
2323+ mutable node_count : int;
2424+ mutable current : Event.spanned option;
2525+ _anchors : (string, Jsont.json) Hashtbl.t; (* For future anchor resolution *)
2626+ meta_none : Jsont.Meta.t;
2727+}
2828+2929+let make_decoder
3030+ ?(locs = false) ?(layout = false) ?(file = "-")
3131+ ?(max_depth = 100) ?(max_nodes = 10_000_000) parser =
3232+ let meta_none = Jsont.Meta.make (Jsont.Textloc.(set_file none) file) in
3333+ { parser; file; locs; _layout = layout; max_depth; max_nodes;
3434+ node_count = 0; current = None;
3535+ _anchors = Hashtbl.create 16; meta_none }
3636+3737+(* Decoder helpers *)
3838+3939+let check_depth d ~nest =
4040+ if nest > d.max_depth then
4141+ Jsont.Error.msgf Jsont.Meta.none "Maximum nesting depth %d exceeded" d.max_depth
4242+4343+let check_nodes d =
4444+ d.node_count <- d.node_count + 1;
4545+ if d.node_count > d.max_nodes then
4646+ Jsont.Error.msgf Jsont.Meta.none "Maximum node count %d exceeded" d.max_nodes
4747+4848+let meta_of_span d span =
4949+ if not d.locs then d.meta_none else
5050+ let start = span.Span.start and stop = span.Span.stop in
5151+ let first_byte = start.Position.index in
5252+ let last_byte = max first_byte (stop.Position.index - 1) in
5353+ (* line_pos is (line_number, byte_position_of_line_start) *)
5454+ let first_line = (start.Position.line, start.Position.index - start.Position.column + 1) in
5555+ let last_line = (stop.Position.line, stop.Position.index - stop.Position.column + 1) in
5656+ let textloc = Jsont.Textloc.make ~file:d.file
5757+ ~first_byte ~last_byte ~first_line ~last_line in
5858+ Jsont.Meta.make textloc
5959+6060+let next_event d =
6161+ d.current <- Parser.next d.parser;
6262+ d.current
6363+6464+let peek_event d =
6565+ match d.current with
6666+ | Some _ -> d.current
6767+ | None -> next_event d
6868+6969+let skip_event d =
7070+ d.current <- None
7171+7272+let _expect_event d pred name =
7373+ match peek_event d with
7474+ | Some ev when pred ev.Event.event -> skip_event d; ev
7575+ | Some ev ->
7676+ let span = ev.Event.span in
7777+ let meta = meta_of_span d span in
7878+ Jsont.Error.msgf meta "Expected %s but found %a" name Event.pp ev.Event.event
7979+ | None ->
8080+ Jsont.Error.msgf Jsont.Meta.none "Expected %s but reached end of stream" name
8181+8282+(* Error helpers *)
8383+8484+let _err_expected_scalar d ev =
8585+ let meta = meta_of_span d ev.Event.span in
8686+ Jsont.Error.msgf meta "Expected scalar but found %a" Event.pp ev.Event.event
8787+8888+let err_type_mismatch d span t ~fnd =
8989+ let meta = meta_of_span d span in
9090+ Jsont.Error.msgf meta "Expected %s but found %s"
9191+ (Jsont.Repr.kinded_sort t) fnd
9292+9393+(* YAML scalar resolution *)
9494+9595+let is_null_scalar s =
9696+ s = "" || s = "~" ||
9797+ s = "null" || s = "Null" || s = "NULL"
9898+9999+let bool_of_scalar_opt s =
100100+ match s with
101101+ | "true" | "True" | "TRUE"
102102+ | "yes" | "Yes" | "YES"
103103+ | "on" | "On" | "ON" -> Some true
104104+ | "false" | "False" | "FALSE"
105105+ | "no" | "No" | "NO"
106106+ | "off" | "Off" | "OFF" -> Some false
107107+ | _ -> None
108108+109109+let float_of_scalar_opt s =
110110+ (* Handle YAML special floats *)
111111+ match s with
112112+ | ".inf" | ".Inf" | ".INF" -> Some Float.infinity
113113+ | "+.inf" | "+.Inf" | "+.INF" -> Some Float.infinity
114114+ | "-.inf" | "-.Inf" | "-.INF" -> Some Float.neg_infinity
115115+ | ".nan" | ".NaN" | ".NAN" -> Some Float.nan
116116+ | _ ->
117117+ (* Try parsing as number, allowing underscores *)
118118+ let s' = String.concat "" (String.split_on_char '_' s) in
119119+ (* Try int first (supports 0o, 0x, 0b) then float *)
120120+ match int_of_string_opt s' with
121121+ | Some i -> Some (float_of_int i)
122122+ | None -> float_of_string_opt s'
123123+124124+let _int_of_scalar_opt s =
125125+ (* Handle hex, octal, and regular integers with underscores *)
126126+ let s' = String.concat "" (String.split_on_char '_' s) in
127127+ int_of_string_opt s'
128128+129129+(* Decode a scalar value according to expected type *)
130130+let rec decode_scalar_as :
131131+ type a. decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a =
132132+ fun d ev value style t ->
133133+ check_nodes d;
134134+ let meta = meta_of_span d ev.Event.span in
135135+ match t with
136136+ | Null map ->
137137+ if is_null_scalar value then map.dec meta ()
138138+ else err_type_mismatch d ev.span t ~fnd:("scalar " ^ value)
139139+ | Bool map ->
140140+ (match bool_of_scalar_opt value with
141141+ | Some b -> map.dec meta b
142142+ | None ->
143143+ (* For explicitly quoted strings, fail *)
144144+ if style <> `Plain then
145145+ err_type_mismatch d ev.span t ~fnd:("string " ^ value)
146146+ else
147147+ err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
148148+ | Number map ->
149149+ (* Handle null -> nan mapping like jsont *)
150150+ if is_null_scalar value then map.dec meta Float.nan
151151+ else
152152+ (match float_of_scalar_opt value with
153153+ | Some f -> map.dec meta f
154154+ | None -> err_type_mismatch d ev.span t ~fnd:("scalar " ^ value))
155155+ | String map ->
156156+ (* Don't decode null values as strings - they should fail so outer combinators
157157+ like 'option' or 'any' can handle them properly *)
158158+ if is_null_scalar value then
159159+ err_type_mismatch d ev.span t ~fnd:"null"
160160+ else
161161+ (* Strings accept any non-null scalar value *)
162162+ map.dec meta value
163163+ | Map m ->
164164+ (* Handle Map combinators (e.g., from Jsont.option) *)
165165+ m.dec (decode_scalar_as d ev value style m.dom)
166166+ | Rec lazy_t ->
167167+ (* Handle recursive types *)
168168+ decode_scalar_as d ev value style (Lazy.force lazy_t)
169169+ | _ ->
170170+ err_type_mismatch d ev.span t ~fnd:"scalar"
171171+172172+(* Forward declaration for mutual recursion *)
173173+let rec decode : type a. decoder -> nest:int -> a t -> a =
174174+ fun d ~nest t ->
175175+ check_depth d ~nest;
176176+ match peek_event d with
177177+ | None -> Jsont.Error.msgf Jsont.Meta.none "Unexpected end of YAML stream"
178178+ | Some ev ->
179179+ match ev.Event.event, t with
180180+ (* Scalar events *)
181181+ | Event.Scalar { value; style; anchor; _ }, _ ->
182182+ skip_event d;
183183+ let result = decode_scalar d ~nest ev value style t in
184184+ (* Store anchor if present - TODO: implement anchor storage *)
185185+ (match anchor with
186186+ | Some _name ->
187187+ (* We need generic JSON for anchors - decode as json and convert back *)
188188+ ()
189189+ | None -> ());
190190+ result
191191+192192+ (* Alias *)
193193+ | Event.Alias { anchor }, _ ->
194194+ skip_event d;
195195+ decode_alias d ev anchor t
196196+197197+ (* Map combinator - must come before specific event matches *)
198198+ | _, Map m ->
199199+ m.dec (decode d ~nest m.dom)
200200+201201+ (* Recursive types - must come before specific event matches *)
202202+ | _, Rec lazy_t ->
203203+ decode d ~nest (Lazy.force lazy_t)
204204+205205+ (* Sequence -> Array *)
206206+ | Event.Sequence_start _, Array map ->
207207+ decode_array d ~nest ev map
208208+209209+ | Event.Sequence_start _, Any map ->
210210+ decode_any_sequence d ~nest ev t map
211211+212212+ | Event.Sequence_start _, _ ->
213213+ err_type_mismatch d ev.span t ~fnd:"sequence"
214214+215215+ (* Mapping -> Object *)
216216+ | Event.Mapping_start _, Object map ->
217217+ decode_object d ~nest ev map
218218+219219+ | Event.Mapping_start _, Any map ->
220220+ decode_any_mapping d ~nest ev t map
221221+222222+ | Event.Mapping_start _, _ ->
223223+ err_type_mismatch d ev.span t ~fnd:"mapping"
224224+225225+ (* Unexpected events *)
226226+ | Event.Sequence_end, _ ->
227227+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected sequence end"
228228+ | Event.Mapping_end, _ ->
229229+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected mapping end"
230230+ | Event.Document_start _, _ ->
231231+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected document start"
232232+ | Event.Document_end _, _ ->
233233+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected document end"
234234+ | Event.Stream_start _, _ ->
235235+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected stream start"
236236+ | Event.Stream_end, _ ->
237237+ Jsont.Error.msgf (meta_of_span d ev.span) "Unexpected stream end"
238238+239239+and decode_scalar : type a. decoder -> nest:int -> Event.spanned -> string -> Scalar_style.t -> a t -> a =
240240+ fun d ~nest ev value style t ->
241241+ match t with
242242+ | Any map -> decode_any_scalar d ev value style t map
243243+ | Map m -> m.dec (decode_scalar d ~nest ev value style m.dom)
244244+ | Rec lazy_t -> decode_scalar d ~nest ev value style (Lazy.force lazy_t)
245245+ | _ -> decode_scalar_as d ev value style t
246246+247247+and decode_any_scalar : type a. decoder -> Event.spanned -> string -> Scalar_style.t -> a t -> a any_map -> a =
248248+ fun d ev value style t map ->
249249+ check_nodes d;
250250+ (* Determine which decoder to use based on scalar content *)
251251+ if is_null_scalar value then
252252+ match map.dec_null with
253253+ | Some t' -> decode_scalar_as d ev value style t'
254254+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Null
255255+ else if style = `Plain then
256256+ (* Try bool, then number, then string *)
257257+ match bool_of_scalar_opt value with
258258+ | Some _ ->
259259+ (match map.dec_bool with
260260+ | Some t' -> decode_scalar_as d ev value style t'
261261+ | None ->
262262+ match map.dec_string with
263263+ | Some t' -> decode_scalar_as d ev value style t'
264264+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Bool)
265265+ | None ->
266266+ match float_of_scalar_opt value with
267267+ | Some _ ->
268268+ (match map.dec_number with
269269+ | Some t' -> decode_scalar_as d ev value style t'
270270+ | None ->
271271+ match map.dec_string with
272272+ | Some t' -> decode_scalar_as d ev value style t'
273273+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Number)
274274+ | None ->
275275+ (* Plain scalar that's not bool/number -> string *)
276276+ match map.dec_string with
277277+ | Some t' -> decode_scalar_as d ev value style t'
278278+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.String
279279+ else
280280+ (* Quoted scalars are strings *)
281281+ match map.dec_string with
282282+ | Some t' -> decode_scalar_as d ev value style t'
283283+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.String
284284+285285+and decode_alias : type a. decoder -> Event.spanned -> string -> a t -> a =
286286+ fun d ev anchor t ->
287287+ check_nodes d;
288288+ match Hashtbl.find_opt d._anchors anchor with
289289+ | None ->
290290+ let meta = meta_of_span d ev.span in
291291+ Jsont.Error.msgf meta "Unknown anchor: %s" anchor
292292+ | Some json ->
293293+ (* Decode the stored JSON value through the type *)
294294+ let t' = Jsont.Repr.unsafe_to_t t in
295295+ match Jsont.Json.decode' t' json with
296296+ | Ok v -> v
297297+ | Error e -> raise (Jsont.Error e)
298298+299299+and decode_array : type a elt b. decoder -> nest:int -> Event.spanned -> (a, elt, b) array_map -> a =
300300+ fun d ~nest start_ev map ->
301301+ skip_event d; (* consume Sequence_start *)
302302+ check_nodes d;
303303+ let meta = meta_of_span d start_ev.span in
304304+ let builder = ref (map.dec_empty ()) in
305305+ let idx = ref 0 in
306306+ let rec loop () =
307307+ match peek_event d with
308308+ | Some { Event.event = Event.Sequence_end; span } ->
309309+ skip_event d;
310310+ let end_meta = meta_of_span d span in
311311+ map.dec_finish end_meta !idx !builder
312312+ | Some _ ->
313313+ let i = !idx in
314314+ (try
315315+ if map.dec_skip i !builder then begin
316316+ (* Skip this element by decoding as ignore *)
317317+ let _ : unit = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) in
318318+ ()
319319+ end else begin
320320+ let elt = decode d ~nest:(nest + 1) map.elt in
321321+ builder := map.dec_add i elt !builder
322322+ end
323323+ with Jsont.Error e ->
324324+ let imeta = Jsont.Meta.none in
325325+ Jsont.Repr.error_push_array meta map (i, imeta) e);
326326+ incr idx;
327327+ loop ()
328328+ | None ->
329329+ Jsont.Error.msgf meta "Unclosed sequence"
330330+ in
331331+ loop ()
332332+333333+and decode_any_sequence : type a. decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
334334+ fun d ~nest ev t map ->
335335+ match map.dec_array with
336336+ | Some t' ->
337337+ (* The t' decoder might be wrapped (e.g., Map for option types)
338338+ Directly decode the array and let the wrapper handle it *)
339339+ (match t' with
340340+ | Array array_map ->
341341+ decode_array d ~nest ev array_map
342342+ | _ ->
343343+ (* For wrapped types like Map (Array ...), use full decode *)
344344+ decode d ~nest t')
345345+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Array
346346+347347+and decode_object : type o. decoder -> nest:int -> Event.spanned -> (o, o) object_map -> o =
348348+ fun d ~nest start_ev map ->
349349+ skip_event d; (* consume Mapping_start *)
350350+ check_nodes d;
351351+ let meta = meta_of_span d start_ev.span in
352352+ let dict = decode_object_members d ~nest meta map String_map.empty Dict.empty in
353353+ let dict = Dict.add object_meta_arg meta dict in
354354+ apply_dict map.dec dict
355355+356356+and decode_object_members : type o.
357357+ decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
358358+ mem_dec String_map.t -> Dict.t -> Dict.t =
359359+ fun d ~nest obj_meta map mem_miss dict ->
360360+ (* Merge expected member decoders *)
361361+ let u _ _ _ = assert false in
362362+ let mem_miss = String_map.union u mem_miss map.mem_decs in
363363+ match map.shape with
364364+ | Object_basic umems ->
365365+ decode_object_basic d ~nest obj_meta map umems mem_miss dict
366366+ | Object_cases (umems_opt, cases) ->
367367+ (* Wrap umems_opt to hide existential types *)
368368+ let umems = Unknown_mems umems_opt in
369369+ decode_object_cases d ~nest obj_meta map umems cases mem_miss [] dict
370370+371371+and decode_object_basic : type o mems builder.
372372+ decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
373373+ (o, mems, builder) unknown_mems ->
374374+ mem_dec String_map.t -> Dict.t -> Dict.t =
375375+ fun d ~nest obj_meta map umems mem_miss dict ->
376376+ let ubuilder = ref (match umems with
377377+ | Unknown_skip | Unknown_error -> Obj.magic ()
378378+ | Unknown_keep (mmap, _) -> mmap.dec_empty ()) in
379379+ let mem_miss = ref mem_miss in
380380+ let dict = ref dict in
381381+ let rec loop () =
382382+ match peek_event d with
383383+ | Some { Event.event = Event.Mapping_end; _ } ->
384384+ skip_event d;
385385+ (* Finalize *)
386386+ finish_object obj_meta map umems !ubuilder !mem_miss !dict
387387+ | Some ev ->
388388+ (* Expect a scalar key *)
389389+ let name, name_meta = decode_mapping_key d ev in
390390+ (* Look up member decoder *)
391391+ (match String_map.find_opt name map.mem_decs with
392392+ | Some (Mem_dec mem) ->
393393+ mem_miss := String_map.remove name !mem_miss;
394394+ (try
395395+ let v = decode d ~nest:(nest + 1) mem.type' in
396396+ dict := Dict.add mem.id v !dict
397397+ with Jsont.Error e ->
398398+ Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
399399+ | None ->
400400+ (* Unknown member *)
401401+ match umems with
402402+ | Unknown_skip ->
403403+ let _ : unit = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.ignore) in
404404+ ()
405405+ | Unknown_error ->
406406+ Jsont.Repr.unexpected_mems_error obj_meta map ~fnd:[(name, name_meta)]
407407+ | Unknown_keep (mmap, _) ->
408408+ (try
409409+ let v = decode d ~nest:(nest + 1) mmap.mems_type in
410410+ ubuilder := mmap.dec_add name_meta name v !ubuilder
411411+ with Jsont.Error e ->
412412+ Jsont.Repr.error_push_object obj_meta map (name, name_meta) e));
413413+ loop ()
414414+ | None ->
415415+ Jsont.Error.msgf obj_meta "Unclosed mapping"
416416+ in
417417+ loop ()
418418+419419+and finish_object : type o mems builder.
420420+ Jsont.Meta.t -> (o, o) object_map -> (o, mems, builder) unknown_mems ->
421421+ builder -> mem_dec String_map.t -> Dict.t -> Dict.t =
422422+ fun meta map umems ubuilder mem_miss dict ->
423423+ let dict = Dict.add object_meta_arg meta dict in
424424+ let dict = match umems with
425425+ | Unknown_skip | Unknown_error -> dict
426426+ | Unknown_keep (mmap, _) -> Dict.add mmap.id (mmap.dec_finish meta ubuilder) dict
427427+ in
428428+ (* Check for missing required members *)
429429+ let add_default _ (Mem_dec mem_map) dict =
430430+ match mem_map.dec_absent with
431431+ | Some v -> Dict.add mem_map.id v dict
432432+ | None -> raise Exit
433433+ in
434434+ try String_map.fold add_default mem_miss dict
435435+ with Exit ->
436436+ let no_default _ (Mem_dec mm) = Option.is_none mm.dec_absent in
437437+ let exp = String_map.filter no_default mem_miss in
438438+ Jsont.Repr.missing_mems_error meta map ~exp ~fnd:[]
439439+440440+and decode_object_cases : type o cases tag.
441441+ decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
442442+ unknown_mems_option ->
443443+ (o, cases, tag) object_cases ->
444444+ mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
445445+ fun d ~nest obj_meta map umems cases mem_miss delayed dict ->
446446+ match peek_event d with
447447+ | Some { Event.event = Event.Mapping_end; _ } ->
448448+ skip_event d;
449449+ (* No tag found - use dec_absent if available *)
450450+ (match cases.tag.dec_absent with
451451+ | Some tag ->
452452+ decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss delayed dict
453453+ | None ->
454454+ (* Missing required case tag *)
455455+ let exp = String_map.singleton cases.tag.name (Mem_dec cases.tag) in
456456+ let fnd = List.map (fun ((n, _), _) -> n) delayed in
457457+ Jsont.Repr.missing_mems_error obj_meta map ~exp ~fnd)
458458+ | Some ev ->
459459+ let name, name_meta = decode_mapping_key d ev in
460460+ if String.equal name cases.tag.name then begin
461461+ (* Found the case tag *)
462462+ let tag = decode d ~nest:(nest + 1) cases.tag.type' in
463463+ decode_with_case_tag d ~nest obj_meta map umems cases tag mem_miss delayed dict
464464+ end else begin
465465+ (* Not the case tag - check if known member or delay *)
466466+ match String_map.find_opt name map.mem_decs with
467467+ | Some (Mem_dec mem) ->
468468+ let mem_miss = String_map.remove name mem_miss in
469469+ (try
470470+ let v = decode d ~nest:(nest + 1) mem.type' in
471471+ let dict = Dict.add mem.id v dict in
472472+ decode_object_cases d ~nest obj_meta map umems cases mem_miss delayed dict
473473+ with Jsont.Error e ->
474474+ Jsont.Repr.error_push_object obj_meta map (name, name_meta) e)
475475+ | None ->
476476+ (* Unknown member - decode as generic JSON and delay *)
477477+ let v = decode d ~nest:(nest + 1) (Jsont.Repr.of_t Jsont.json) in
478478+ let delayed = ((name, name_meta), v) :: delayed in
479479+ decode_object_cases d ~nest obj_meta map umems cases mem_miss delayed dict
480480+ end
481481+ | None ->
482482+ Jsont.Error.msgf obj_meta "Unclosed mapping"
483483+484484+and decode_with_case_tag : type o cases tag.
485485+ decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
486486+ unknown_mems_option ->
487487+ (o, cases, tag) object_cases -> tag ->
488488+ mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
489489+ fun d ~nest obj_meta map umems cases tag mem_miss delayed dict ->
490490+ let eq_tag (Case c) = cases.tag_compare c.tag tag = 0 in
491491+ match List.find_opt eq_tag cases.cases with
492492+ | None ->
493493+ Jsont.Repr.unexpected_case_tag_error obj_meta map cases tag
494494+ | Some (Case case) ->
495495+ (* Continue decoding with the case's object map *)
496496+ let case_dict = decode_case_remaining d ~nest obj_meta case.object_map
497497+ umems mem_miss delayed dict in
498498+ let case_value = apply_dict case.object_map.dec case_dict in
499499+ Dict.add cases.id (case.dec case_value) dict
500500+501501+and decode_case_remaining : type o.
502502+ decoder -> nest:int -> Jsont.Meta.t -> (o, o) object_map ->
503503+ unknown_mems_option ->
504504+ mem_dec String_map.t -> (Jsont.name * Jsont.json) list -> Dict.t -> Dict.t =
505505+ fun d ~nest obj_meta case_map _umems mem_miss delayed dict ->
506506+ (* First, process delayed members against the case map *)
507507+ let u _ _ _ = assert false in
508508+ let mem_miss = String_map.union u mem_miss case_map.mem_decs in
509509+ let dict, mem_miss = List.fold_left (fun (dict, mem_miss) ((name, meta), json) ->
510510+ match String_map.find_opt name case_map.mem_decs with
511511+ | Some (Mem_dec mem) ->
512512+ let t' = Jsont.Repr.unsafe_to_t mem.type' in
513513+ (match Jsont.Json.decode' t' json with
514514+ | Ok v ->
515515+ let dict = Dict.add mem.id v dict in
516516+ let mem_miss = String_map.remove name mem_miss in
517517+ (dict, mem_miss)
518518+ | Error e ->
519519+ Jsont.Repr.error_push_object obj_meta case_map (name, meta) e)
520520+ | None ->
521521+ (* Unknown for case too - skip them *)
522522+ (dict, mem_miss)
523523+ ) (dict, mem_miss) delayed in
524524+ (* Then continue reading remaining members using case's own unknown handling *)
525525+ match case_map.shape with
526526+ | Object_basic case_umems ->
527527+ decode_object_basic d ~nest obj_meta case_map case_umems mem_miss dict
528528+ | Object_cases _ ->
529529+ (* Nested cases shouldn't happen - use skip for safety *)
530530+ decode_object_basic d ~nest obj_meta case_map Unknown_skip mem_miss dict
531531+532532+and decode_any_mapping : type a. decoder -> nest:int -> Event.spanned -> a t -> a any_map -> a =
533533+ fun d ~nest ev t map ->
534534+ match map.dec_object with
535535+ | Some t' -> decode d ~nest t'
536536+ | None -> Jsont.Repr.type_error (meta_of_span d ev.span) t ~fnd:Jsont.Sort.Object
537537+538538+and decode_mapping_key : decoder -> Event.spanned -> string * Jsont.Meta.t =
539539+ fun d ev ->
540540+ match ev.Event.event with
541541+ | Event.Scalar { value; _ } ->
542542+ skip_event d;
543543+ let meta = meta_of_span d ev.span in
544544+ (value, meta)
545545+ | _ ->
546546+ let meta = meta_of_span d ev.span in
547547+ Jsont.Error.msgf meta "Mapping keys must be scalars (strings), found %a"
548548+ Event.pp ev.event
549549+550550+(* Skip stream/document wrappers *)
551551+let skip_to_content d =
552552+ let rec loop () =
553553+ match peek_event d with
554554+ | Some { Event.event = Event.Stream_start _; _ } -> skip_event d; loop ()
555555+ | Some { Event.event = Event.Document_start _; _ } -> skip_event d; loop ()
556556+ | _ -> ()
557557+ in
558558+ loop ()
559559+560560+let skip_end_wrappers d =
561561+ let rec loop () =
562562+ match peek_event d with
563563+ | Some { Event.event = Event.Document_end _; _ } -> skip_event d; loop ()
564564+ | Some { Event.event = Event.Stream_end; _ } -> skip_event d; loop ()
565565+ | None -> ()
566566+ | Some ev ->
567567+ let meta = meta_of_span d ev.span in
568568+ Jsont.Error.msgf meta "Expected end of document but found %a" Event.pp ev.event
569569+ in
570570+ loop ()
571571+572572+(* Public decode API *)
573573+574574+let decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader =
575575+ let parser = Parser.of_reader reader in
576576+ let d = make_decoder ?layout ?locs ?file ?max_depth ?max_nodes parser in
577577+ try
578578+ skip_to_content d;
579579+ let t' = Jsont.Repr.of_t t in
580580+ let v = decode d ~nest:0 t' in
581581+ skip_end_wrappers d;
582582+ Ok v
583583+ with
584584+ | Jsont.Error e -> Error e
585585+ | Error.Yamlrw_error err ->
586586+ let msg = Error.to_string err in
587587+ Error (Jsont.Error.make_msg Jsont.Error.Context.empty Jsont.Meta.none msg)
588588+589589+let decode ?layout ?locs ?file ?max_depth ?max_nodes t reader =
590590+ Result.map_error Jsont.Error.to_string
591591+ (decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader)
592592+593593+let decode_string' ?layout ?locs ?file ?max_depth ?max_nodes t s =
594594+ decode' ?layout ?locs ?file ?max_depth ?max_nodes t (Bytes.Reader.of_string s)
595595+596596+let decode_string ?layout ?locs ?file ?max_depth ?max_nodes t s =
597597+ decode ?layout ?locs ?file ?max_depth ?max_nodes t (Bytes.Reader.of_string s)
598598+599599+(* Encoder *)
600600+601601+type encoder = {
602602+ emitter : Emitter.t;
603603+ format : yaml_format;
604604+ _indent : int; (* Stored for future use in custom formatting *)
605605+ explicit_doc : bool;
606606+ scalar_style : Scalar_style.t;
607607+}
608608+609609+let make_encoder
610610+ ?(format = Block) ?(indent = 2) ?(explicit_doc = false)
611611+ ?(scalar_style = `Any) emitter =
612612+ { emitter; format; _indent = indent; explicit_doc; scalar_style }
613613+614614+let layout_style_of_format = function
615615+ | Block -> `Block
616616+ | Flow -> `Flow
617617+ | Layout -> `Any
618618+619619+(* Choose appropriate scalar style for a string *)
620620+let choose_scalar_style ~preferred s =
621621+ if preferred <> `Any then preferred
622622+ else if String.contains s '\n' then `Literal
623623+ else if String.length s > 80 then `Folded
624624+ else `Plain
625625+626626+(* Encode null *)
627627+let encode_null e _meta =
628628+ Emitter.emit e.emitter (Event.Scalar {
629629+ anchor = None;
630630+ tag = None;
631631+ value = "null";
632632+ plain_implicit = true;
633633+ quoted_implicit = true;
634634+ style = `Plain;
635635+ })
636636+637637+(* Encode boolean *)
638638+let encode_bool e _meta b =
639639+ Emitter.emit e.emitter (Event.Scalar {
640640+ anchor = None;
641641+ tag = None;
642642+ value = if b then "true" else "false";
643643+ plain_implicit = true;
644644+ quoted_implicit = true;
645645+ style = `Plain;
646646+ })
647647+648648+(* Encode number *)
649649+let encode_number e _meta f =
650650+ let value =
651651+ if Float.is_nan f then ".nan"
652652+ else if f = Float.infinity then ".inf"
653653+ else if f = Float.neg_infinity then "-.inf"
654654+ else
655655+ let s = Printf.sprintf "%.17g" f in
656656+ (* Ensure it looks like a number *)
657657+ if String.contains s '.' || String.contains s 'e' || String.contains s 'E'
658658+ then s
659659+ else s ^ ".0"
660660+ in
661661+ Emitter.emit e.emitter (Event.Scalar {
662662+ anchor = None;
663663+ tag = None;
664664+ value;
665665+ plain_implicit = true;
666666+ quoted_implicit = true;
667667+ style = `Plain;
668668+ })
669669+670670+(* Encode string *)
671671+let encode_string e _meta s =
672672+ let style = choose_scalar_style ~preferred:e.scalar_style s in
673673+ Emitter.emit e.emitter (Event.Scalar {
674674+ anchor = None;
675675+ tag = None;
676676+ value = s;
677677+ plain_implicit = true;
678678+ quoted_implicit = true;
679679+ style;
680680+ })
681681+682682+let rec encode : type a. encoder -> a t -> a -> unit =
683683+ fun e t v ->
684684+ match t with
685685+ | Null map ->
686686+ let meta = map.enc_meta v in
687687+ let () = map.enc v in
688688+ encode_null e meta
689689+690690+ | Bool map ->
691691+ let meta = map.enc_meta v in
692692+ let b = map.enc v in
693693+ encode_bool e meta b
694694+695695+ | Number map ->
696696+ let meta = map.enc_meta v in
697697+ let f = map.enc v in
698698+ encode_number e meta f
699699+700700+ | String map ->
701701+ let meta = map.enc_meta v in
702702+ let s = map.enc v in
703703+ encode_string e meta s
704704+705705+ | Array map ->
706706+ encode_array e map v
707707+708708+ | Object map ->
709709+ encode_object e map v
710710+711711+ | Any map ->
712712+ let t' = map.enc v in
713713+ encode e t' v
714714+715715+ | Map m ->
716716+ encode e m.dom (m.enc v)
717717+718718+ | Rec lazy_t ->
719719+ encode e (Lazy.force lazy_t) v
720720+721721+and encode_array : type a elt b. encoder -> (a, elt, b) array_map -> a -> unit =
722722+ fun e map v ->
723723+ let style = layout_style_of_format e.format in
724724+ Emitter.emit e.emitter (Event.Sequence_start {
725725+ anchor = None;
726726+ tag = None;
727727+ implicit = true;
728728+ style;
729729+ });
730730+ let _ = map.enc (fun () _idx elt ->
731731+ encode e map.elt elt;
732732+ ()
733733+ ) () v in
734734+ Emitter.emit e.emitter Event.Sequence_end
735735+736736+and encode_object : type o. encoder -> (o, o) object_map -> o -> unit =
737737+ fun e map v ->
738738+ let style = layout_style_of_format e.format in
739739+ Emitter.emit e.emitter (Event.Mapping_start {
740740+ anchor = None;
741741+ tag = None;
742742+ implicit = true;
743743+ style;
744744+ });
745745+ (* Encode each member *)
746746+ List.iter (fun (Mem_enc mem) ->
747747+ let mem_v = mem.enc v in
748748+ if not (mem.enc_omit mem_v) then begin
749749+ (* Emit key *)
750750+ Emitter.emit e.emitter (Event.Scalar {
751751+ anchor = None;
752752+ tag = None;
753753+ value = mem.name;
754754+ plain_implicit = true;
755755+ quoted_implicit = true;
756756+ style = `Plain;
757757+ });
758758+ (* Emit value *)
759759+ encode e mem.type' mem_v
760760+ end
761761+ ) map.mem_encs;
762762+ (* Handle case objects *)
763763+ (match map.shape with
764764+ | Object_basic _ -> ()
765765+ | Object_cases (_, cases) ->
766766+ let Case_value (case_map, case_v) = cases.enc_case (cases.enc v) in
767767+ (* Emit case tag *)
768768+ if not (cases.tag.enc_omit (case_map.tag)) then begin
769769+ Emitter.emit e.emitter (Event.Scalar {
770770+ anchor = None;
771771+ tag = None;
772772+ value = cases.tag.name;
773773+ plain_implicit = true;
774774+ quoted_implicit = true;
775775+ style = `Plain;
776776+ });
777777+ encode e cases.tag.type' case_map.tag
778778+ end;
779779+ (* Emit case members *)
780780+ List.iter (fun (Mem_enc mem) ->
781781+ let mem_v = mem.enc case_v in
782782+ if not (mem.enc_omit mem_v) then begin
783783+ Emitter.emit e.emitter (Event.Scalar {
784784+ anchor = None;
785785+ tag = None;
786786+ value = mem.name;
787787+ plain_implicit = true;
788788+ quoted_implicit = true;
789789+ style = `Plain;
790790+ });
791791+ encode e mem.type' mem_v
792792+ end
793793+ ) case_map.object_map.mem_encs);
794794+ Emitter.emit e.emitter Event.Mapping_end
795795+796796+(* Public encode API *)
797797+798798+let encode' ?buf:_ ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer =
799799+ let config = {
800800+ Emitter.default_config with
801801+ indent = Option.value ~default:2 indent;
802802+ layout_style = (match format with
803803+ | Some Flow -> `Flow
804804+ | _ -> `Block);
805805+ } in
806806+ let emitter = Emitter.of_writer ~config writer in
807807+ let e = make_encoder ?format ?indent ?explicit_doc ?scalar_style emitter in
808808+ try
809809+ Emitter.emit e.emitter (Event.Stream_start { encoding = `Utf8 });
810810+ Emitter.emit e.emitter (Event.Document_start {
811811+ version = None;
812812+ implicit = not e.explicit_doc;
813813+ });
814814+ let t' = Jsont.Repr.of_t t in
815815+ encode e t' v;
816816+ Emitter.emit e.emitter (Event.Document_end { implicit = not e.explicit_doc });
817817+ Emitter.emit e.emitter Event.Stream_end;
818818+ if eod then Emitter.flush e.emitter;
819819+ Ok ()
820820+ with
821821+ | Jsont.Error err -> Error err
822822+ | Error.Yamlrw_error err ->
823823+ let msg = Error.to_string err in
824824+ Error (Jsont.Error.make_msg Jsont.Error.Context.empty Jsont.Meta.none msg)
825825+826826+let encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer =
827827+ Result.map_error Jsont.Error.to_string
828828+ (encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer)
829829+830830+let encode_string' ?buf ?format ?indent ?explicit_doc ?scalar_style t v =
831831+ let b = Buffer.create 256 in
832832+ let writer = Bytes.Writer.of_buffer b in
833833+ match encode' ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod:true writer with
834834+ | Ok () -> Ok (Buffer.contents b)
835835+ | Error e -> Error e
836836+837837+let encode_string ?buf ?format ?indent ?explicit_doc ?scalar_style t v =
838838+ Result.map_error Jsont.Error.to_string
839839+ (encode_string' ?buf ?format ?indent ?explicit_doc ?scalar_style t v)
840840+841841+(* Recode *)
842842+843843+let recode ?layout ?locs ?file ?max_depth ?max_nodes
844844+ ?buf ?format ?indent ?explicit_doc ?scalar_style t reader writer ~eod =
845845+ let format = match layout, format with
846846+ | Some true, None -> Some Layout
847847+ | _, f -> f
848848+ in
849849+ let layout = match layout, format with
850850+ | None, Some Layout -> Some true
851851+ | l, _ -> l
852852+ in
853853+ match decode' ?layout ?locs ?file ?max_depth ?max_nodes t reader with
854854+ | Ok v -> encode ?buf ?format ?indent ?explicit_doc ?scalar_style t v ~eod writer
855855+ | Error e -> Error (Jsont.Error.to_string e)
856856+857857+let recode_string ?layout ?locs ?file ?max_depth ?max_nodes
858858+ ?buf ?format ?indent ?explicit_doc ?scalar_style t s =
859859+ let format = match layout, format with
860860+ | Some true, None -> Some Layout
861861+ | _, f -> f
862862+ in
863863+ let layout = match layout, format with
864864+ | None, Some Layout -> Some true
865865+ | l, _ -> l
866866+ in
867867+ match decode_string' ?layout ?locs ?file ?max_depth ?max_nodes t s with
868868+ | Ok v -> encode_string ?buf ?format ?indent ?explicit_doc ?scalar_style t v
869869+ | Error e -> Error (Jsont.Error.to_string e)
+178
lib/yamlt.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** YAML codec using Jsont type descriptions.
77+88+ This module provides YAML streaming encode/decode that interprets
99+ {!Jsont.t} type descriptions, allowing the same codec definitions
1010+ to work for both JSON and YAML.
1111+1212+ {b Example:}
1313+ {[
1414+ (* Define a codec once using Jsont *)
1515+ module Config = struct
1616+ type t = { name: string; port: int }
1717+ let make name port = { name; port }
1818+ let jsont =
1919+ Jsont.Object.map ~kind:"Config" make
2020+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
2121+ |> Jsont.Object.mem "port" Jsont.int ~enc:(fun c -> c.port)
2222+ |> Jsont.Object.finish
2323+ end
2424+2525+ (* Use the same codec for both JSON and YAML *)
2626+ let from_json = Jsont_bytesrw.decode_string Config.jsont json_str
2727+ let from_yaml = Yamlt.decode_string Config.jsont yaml_str
2828+ ]}
2929+3030+ See notes about {{!yaml_mapping}YAML to JSON mapping} and
3131+ {{!yaml_scalars}YAML scalar resolution}.
3232+*)
3333+3434+open Bytesrw
3535+3636+(** {1:decode Decode} *)
3737+3838+val decode :
3939+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
4040+ ?max_depth:int -> ?max_nodes:int ->
4141+ 'a Jsont.t -> Bytes.Reader.t -> ('a, string) result
4242+(** [decode t r] decodes a value from YAML reader [r] according to type [t].
4343+ {ul
4444+ {- If [layout] is [true], style information is preserved in {!Jsont.Meta.t}
4545+ values (for potential round-tripping). Defaults to [false].}
4646+ {- If [locs] is [true], source locations are preserved in {!Jsont.Meta.t}
4747+ values and error messages are precisely located. Defaults to [false].}
4848+ {- [file] is the file path for error messages.
4949+ Defaults to {!Jsont.Textloc.file_none}.}
5050+ {- [max_depth] limits nesting depth to prevent stack overflow
5151+ (billion laughs protection). Defaults to [100].}
5252+ {- [max_nodes] limits total decoded nodes
5353+ (billion laughs protection). Defaults to [10_000_000].}}
5454+5555+ The YAML input must contain exactly one document. Multi-document
5656+ streams are not supported; use {!decode_all} for those. *)
5757+5858+val decode' :
5959+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
6060+ ?max_depth:int -> ?max_nodes:int ->
6161+ 'a Jsont.t -> Bytes.Reader.t -> ('a, Jsont.Error.t) result
6262+(** [decode'] is like {!val-decode} but preserves the error structure. *)
6363+6464+val decode_string :
6565+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
6666+ ?max_depth:int -> ?max_nodes:int ->
6767+ 'a Jsont.t -> string -> ('a, string) result
6868+(** [decode_string] is like {!val-decode} but decodes directly from a string. *)
6969+7070+val decode_string' :
7171+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
7272+ ?max_depth:int -> ?max_nodes:int ->
7373+ 'a Jsont.t -> string -> ('a, Jsont.Error.t) result
7474+(** [decode_string'] is like {!val-decode'} but decodes directly from a string. *)
7575+7676+(** {1:encode Encode} *)
7777+7878+(** YAML output format. *)
7979+type yaml_format =
8080+ | Block (** Block style (indented) - default. Clean, readable YAML. *)
8181+ | Flow (** Flow style (JSON-like). Compact, single-line collections. *)
8282+ | Layout (** Preserve layout from {!Jsont.Meta.t} when available. *)
8383+8484+val encode :
8585+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
8686+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
8787+ 'a Jsont.t -> 'a -> eod:bool -> Bytes.Writer.t -> (unit, string) result
8888+(** [encode t v w] encodes value [v] according to type [t] to YAML on [w].
8989+ {ul
9090+ {- If [buf] is specified, it is used as a buffer for output slices.
9191+ Defaults to a buffer of length {!Bytesrw.Bytes.Writer.slice_length}[ w].}
9292+ {- [format] controls the output style. Defaults to {!Block}.}
9393+ {- [indent] is the indentation width in spaces. Defaults to [2].}
9494+ {- [explicit_doc] if [true], emits explicit document markers
9595+ ([---] and [...]). Defaults to [false].}
9696+ {- [scalar_style] is the preferred style for string scalars.
9797+ Defaults to [`Any] (auto-detect based on content).}
9898+ {- [eod] indicates whether {!Bytesrw.Bytes.Slice.eod} should be
9999+ written on [w] after encoding.}} *)
100100+101101+val encode' :
102102+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
103103+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
104104+ 'a Jsont.t -> 'a -> eod:bool -> Bytes.Writer.t -> (unit, Jsont.Error.t) result
105105+(** [encode'] is like {!val-encode} but preserves the error structure. *)
106106+107107+val encode_string :
108108+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
109109+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
110110+ 'a Jsont.t -> 'a -> (string, string) result
111111+(** [encode_string] is like {!val-encode} but writes to a string. *)
112112+113113+val encode_string' :
114114+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
115115+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
116116+ 'a Jsont.t -> 'a -> (string, Jsont.Error.t) result
117117+(** [encode_string'] is like {!val-encode'} but writes to a string. *)
118118+119119+(** {1:recode Recode}
120120+121121+ The defaults in these functions are those of {!val-decode} and
122122+ {!val-encode}, except if [layout] is [true], [format] defaults to
123123+ {!Layout} and vice-versa. *)
124124+125125+val recode :
126126+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
127127+ ?max_depth:int -> ?max_nodes:int ->
128128+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
129129+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
130130+ 'a Jsont.t -> Bytes.Reader.t -> Bytes.Writer.t -> eod:bool ->
131131+ (unit, string) result
132132+(** [recode t r w] is {!val-decode} followed by {!val-encode}. *)
133133+134134+val recode_string :
135135+ ?layout:bool -> ?locs:bool -> ?file:Jsont.Textloc.fpath ->
136136+ ?max_depth:int -> ?max_nodes:int ->
137137+ ?buf:Stdlib.Bytes.t -> ?format:yaml_format -> ?indent:int ->
138138+ ?explicit_doc:bool -> ?scalar_style:Yamlrw.Scalar_style.t ->
139139+ 'a Jsont.t -> string -> (string, string) result
140140+(** [recode_string] is like {!val-recode} but operates on strings. *)
141141+142142+(** {1:yaml_mapping YAML to JSON Mapping}
143143+144144+ YAML is a superset of JSON. This module maps YAML structures to
145145+ the JSON data model that {!Jsont.t} describes:
146146+147147+ {ul
148148+ {- YAML scalars map to JSON null, boolean, number, or string
149149+ depending on content and the expected type}
150150+ {- YAML sequences map to JSON arrays}
151151+ {- YAML mappings map to JSON objects (keys must be strings)}
152152+ {- YAML aliases are resolved during decoding}
153153+ {- YAML tags are used to guide type resolution when present}}
154154+155155+ {b Limitations:}
156156+ {ul
157157+ {- Only string keys are supported in mappings (JSON object compatibility)}
158158+ {- Anchors and aliases are resolved; the alias structure is not preserved}
159159+ {- Multi-document streams require {!decode_all}}} *)
160160+161161+(** {1:yaml_scalars YAML Scalar Resolution}
162162+163163+ YAML scalars are resolved to JSON types as follows:
164164+165165+ {b Null:} [null], [Null], [NULL], [~], or empty string
166166+167167+ {b Boolean:} [true], [True], [TRUE], [false], [False], [FALSE],
168168+ [yes], [Yes], [YES], [no], [No], [NO], [on], [On], [ON],
169169+ [off], [Off], [OFF]
170170+171171+ {b Number:} Decimal integers, floats, hex ([0x...]), octal ([0o...]),
172172+ infinity ([.inf], [-.inf]), NaN ([.nan])
173173+174174+ {b String:} Anything else, or explicitly quoted scalars
175175+176176+ When decoding against a specific {!Jsont.t} type, the expected type
177177+ takes precedence over automatic resolution. For example, decoding
178178+ ["yes"] against {!Jsont.string} yields the string ["yes"], not [true]. *)
···11+let () =
22+ let codec1 =
33+ Jsont.Object.map ~kind:"Test" (fun arr -> arr)
44+ |> Jsont.Object.mem "values" (Jsont.array Jsont.string) ~enc:(fun arr -> arr)
55+ |> Jsont.Object.finish
66+ in
77+88+ let yaml1 = "values: [a, b, c]" in
99+1010+ Printf.printf "Test 1: Non-optional array:\n";
1111+ (match Yamlt.decode_string codec1 yaml1 with
1212+ | Ok arr -> Printf.printf "Result: [%d items]\n" (Array.length arr)
1313+ | Error e -> Printf.printf "Error: %s\n" e);
1414+1515+ let codec2 =
1616+ Jsont.Object.map ~kind:"Test" (fun arr -> arr)
1717+ |> Jsont.Object.mem "values" (Jsont.option (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
1818+ |> Jsont.Object.finish
1919+ in
2020+2121+ Printf.printf "\nTest 2: Jsont.option (Jsont.array):\n";
2222+ (match Yamlt.decode_string codec2 yaml1 with
2323+ | Ok arr ->
2424+ (match arr with
2525+ | None -> Printf.printf "Result: None\n"
2626+ | Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
2727+ | Error e -> Printf.printf "Error: %s\n" e)
+330
tests/bin/test_arrays.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test array codec functionality with Yamlt *)
77+88+99+(* Helper to read file *)
1010+let read_file path =
1111+ let ic = open_in path in
1212+ let len = in_channel_length ic in
1313+ let s = really_input_string ic len in
1414+ close_in ic;
1515+ s
1616+1717+(* Helper to show results *)
1818+let show_result label = function
1919+ | Ok v -> Printf.printf "%s: %s\n" label v
2020+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2121+2222+let show_result_both label json_result yaml_result =
2323+ Printf.printf "JSON: ";
2424+ show_result label json_result;
2525+ Printf.printf "YAML: ";
2626+ show_result label yaml_result
2727+2828+(* Test: Simple int array *)
2929+let test_int_array file =
3030+ let module M = struct
3131+ type numbers = { values: int array }
3232+3333+ let numbers_codec =
3434+ Jsont.Object.map ~kind:"Numbers" (fun values -> { values })
3535+ |> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n -> n.values)
3636+ |> Jsont.Object.finish
3737+3838+ let show n =
3939+ Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map string_of_int n.values)))
4040+ end in
4141+4242+ let yaml = read_file file in
4343+ let json = read_file (file ^ ".json") in
4444+ let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
4545+ let yaml_result = Yamlt.decode_string M.numbers_codec yaml in
4646+4747+ show_result_both "int_array"
4848+ (Result.map M.show json_result)
4949+ (Result.map M.show yaml_result)
5050+5151+(* Test: String array *)
5252+let test_string_array file =
5353+ let module M = struct
5454+ type tags = { items: string array }
5555+5656+ let tags_codec =
5757+ Jsont.Object.map ~kind:"Tags" (fun items -> { items })
5858+ |> Jsont.Object.mem "items" (Jsont.array Jsont.string) ~enc:(fun t -> t.items)
5959+ |> Jsont.Object.finish
6060+6161+ let show t =
6262+ Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map (Printf.sprintf "%S") t.items)))
6363+ end in
6464+6565+ let yaml = read_file file in
6666+ let json = read_file (file ^ ".json") in
6767+ let json_result = Jsont_bytesrw.decode_string M.tags_codec json in
6868+ let yaml_result = Yamlt.decode_string M.tags_codec yaml in
6969+7070+ show_result_both "string_array"
7171+ (Result.map M.show json_result)
7272+ (Result.map M.show yaml_result)
7373+7474+(* Test: Float/number array *)
7575+let test_float_array file =
7676+ let module M = struct
7777+ type measurements = { values: float array }
7878+7979+ let measurements_codec =
8080+ Jsont.Object.map ~kind:"Measurements" (fun values -> { values })
8181+ |> Jsont.Object.mem "values" (Jsont.array Jsont.number) ~enc:(fun m -> m.values)
8282+ |> Jsont.Object.finish
8383+8484+ let show m =
8585+ Printf.sprintf "[%s]"
8686+ (String.concat "; " (Array.to_list (Array.map (Printf.sprintf "%.2f") m.values)))
8787+ end in
8888+8989+ let yaml = read_file file in
9090+ let json = read_file (file ^ ".json") in
9191+ let json_result = Jsont_bytesrw.decode_string M.measurements_codec json in
9292+ let yaml_result = Yamlt.decode_string M.measurements_codec yaml in
9393+9494+ show_result_both "float_array"
9595+ (Result.map M.show json_result)
9696+ (Result.map M.show yaml_result)
9797+9898+(* Test: Empty array *)
9999+let test_empty_array file =
100100+ let module M = struct
101101+ type empty = { items: int array }
102102+103103+ let empty_codec =
104104+ Jsont.Object.map ~kind:"Empty" (fun items -> { items })
105105+ |> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun e -> e.items)
106106+ |> Jsont.Object.finish
107107+108108+ let show e =
109109+ Printf.sprintf "length=%d" (Stdlib.Array.length e.items)
110110+ end in
111111+112112+ let yaml = read_file file in
113113+ let json = read_file (file ^ ".json") in
114114+ let json_result = Jsont_bytesrw.decode_string M.empty_codec json in
115115+ let yaml_result = Yamlt.decode_string M.empty_codec yaml in
116116+117117+ show_result_both "empty_array"
118118+ (Result.map M.show json_result)
119119+ (Result.map M.show yaml_result)
120120+121121+(* Test: Array of objects *)
122122+let test_object_array file =
123123+ let module M = struct
124124+ type person = { name: string; age: int }
125125+ type people = { persons: person array }
126126+127127+ let person_codec =
128128+ Jsont.Object.map ~kind:"Person" (fun name age -> { name; age })
129129+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
130130+ |> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
131131+ |> Jsont.Object.finish
132132+133133+ let people_codec =
134134+ Jsont.Object.map ~kind:"People" (fun persons -> { persons })
135135+ |> Jsont.Object.mem "persons" (Jsont.array person_codec) ~enc:(fun p -> p.persons)
136136+ |> Jsont.Object.finish
137137+138138+ let show_person p = Printf.sprintf "{%s,%d}" p.name p.age
139139+ let show ps =
140140+ Printf.sprintf "[%s]"
141141+ (String.concat "; " (Array.to_list (Array.map show_person ps.persons)))
142142+ end in
143143+144144+ let yaml = read_file file in
145145+ let json = read_file (file ^ ".json") in
146146+ let json_result = Jsont_bytesrw.decode_string M.people_codec json in
147147+ let yaml_result = Yamlt.decode_string M.people_codec yaml in
148148+149149+ show_result_both "object_array"
150150+ (Result.map M.show json_result)
151151+ (Result.map M.show yaml_result)
152152+153153+(* Test: Nested arrays *)
154154+let test_nested_arrays file =
155155+ let module M = struct
156156+ type matrix = { data: int array array }
157157+158158+ let matrix_codec =
159159+ Jsont.Object.map ~kind:"Matrix" (fun data -> { data })
160160+ |> Jsont.Object.mem "data" (Jsont.array (Jsont.array Jsont.int))
161161+ ~enc:(fun m -> m.data)
162162+ |> Jsont.Object.finish
163163+164164+ let show_row row =
165165+ Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map string_of_int row)))
166166+167167+ let show m =
168168+ Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map show_row m.data)))
169169+ end in
170170+171171+ let yaml = read_file file in
172172+ let json = read_file (file ^ ".json") in
173173+ let json_result = Jsont_bytesrw.decode_string M.matrix_codec json in
174174+ let yaml_result = Yamlt.decode_string M.matrix_codec yaml in
175175+176176+ show_result_both "nested_arrays"
177177+ (Result.map M.show json_result)
178178+ (Result.map M.show yaml_result)
179179+180180+(* Test: Mixed types in array (should fail with homogeneous codec) *)
181181+let test_type_mismatch file =
182182+ let module M = struct
183183+ type numbers = { values: int array }
184184+185185+ let numbers_codec =
186186+ Jsont.Object.map ~kind:"Numbers" (fun values -> { values })
187187+ |> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun n -> n.values)
188188+ |> Jsont.Object.finish
189189+ end in
190190+191191+ let yaml = read_file file in
192192+ let result = Yamlt.decode_string M.numbers_codec yaml in
193193+ match result with
194194+ | Ok _ -> Printf.printf "Unexpected success\n"
195195+ | Error e -> Printf.printf "Expected error: %s\n" e
196196+197197+(* Test: Bool array *)
198198+let test_bool_array file =
199199+ let module M = struct
200200+ type flags = { values: bool array }
201201+202202+ let flags_codec =
203203+ Jsont.Object.map ~kind:"Flags" (fun values -> { values })
204204+ |> Jsont.Object.mem "values" (Jsont.array Jsont.bool) ~enc:(fun f -> f.values)
205205+ |> Jsont.Object.finish
206206+207207+ let show f =
208208+ Printf.sprintf "[%s]"
209209+ (String.concat "; " (Array.to_list (Array.map string_of_bool f.values)))
210210+ end in
211211+212212+ let yaml = read_file file in
213213+ let json = read_file (file ^ ".json") in
214214+ let json_result = Jsont_bytesrw.decode_string M.flags_codec json in
215215+ let yaml_result = Yamlt.decode_string M.flags_codec yaml in
216216+217217+ show_result_both "bool_array"
218218+ (Result.map M.show json_result)
219219+ (Result.map M.show yaml_result)
220220+221221+(* Test: Array with nulls *)
222222+let test_nullable_array file =
223223+ let module M = struct
224224+ type nullable = { values: string option array }
225225+226226+ let nullable_codec =
227227+ Jsont.Object.map ~kind:"Nullable" (fun values -> { values })
228228+ |> Jsont.Object.mem "values" (Jsont.array (Jsont.some Jsont.string))
229229+ ~enc:(fun n -> n.values)
230230+ |> Jsont.Object.finish
231231+232232+ let show_opt = function
233233+ | None -> "null"
234234+ | Some s -> Printf.sprintf "%S" s
235235+236236+ let show n =
237237+ Printf.sprintf "[%s]" (String.concat "; " (Array.to_list (Array.map show_opt n.values)))
238238+ end in
239239+240240+ let yaml = read_file file in
241241+ let json = read_file (file ^ ".json") in
242242+ let json_result = Jsont_bytesrw.decode_string M.nullable_codec json in
243243+ let yaml_result = Yamlt.decode_string M.nullable_codec yaml in
244244+245245+ show_result_both "nullable_array"
246246+ (Result.map M.show json_result)
247247+ (Result.map M.show yaml_result)
248248+249249+(* Test: Encoding arrays to different formats *)
250250+let test_encode_arrays () =
251251+ let module M = struct
252252+ type data = { numbers: int array; strings: string array }
253253+254254+ let data_codec =
255255+ Jsont.Object.map ~kind:"Data" (fun numbers strings -> { numbers; strings })
256256+ |> Jsont.Object.mem "numbers" (Jsont.array Jsont.int) ~enc:(fun d -> d.numbers)
257257+ |> Jsont.Object.mem "strings" (Jsont.array Jsont.string) ~enc:(fun d -> d.strings)
258258+ |> Jsont.Object.finish
259259+ end in
260260+261261+ let data = { M.numbers = [|1; 2; 3; 4; 5|]; strings = [|"hello"; "world"|] } in
262262+263263+ (* Encode to JSON *)
264264+ (match Jsont_bytesrw.encode_string M.data_codec data with
265265+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
266266+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
267267+268268+ (* Encode to YAML Block *)
269269+ (match Yamlt.encode_string ~format:Yamlt.Block M.data_codec data with
270270+ | Ok s -> Printf.printf "YAML Block:\n%s" s
271271+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
272272+273273+ (* Encode to YAML Flow *)
274274+ (match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
275275+ | Ok s -> Printf.printf "YAML Flow: %s" s
276276+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
277277+278278+let () =
279279+ let usage = "Usage: test_arrays <command> [args...]" in
280280+281281+ if Array.length Sys.argv < 2 then begin
282282+ prerr_endline usage;
283283+ exit 1
284284+ end;
285285+286286+ match Sys.argv.(1) with
287287+ | "int" when Array.length Sys.argv = 3 ->
288288+ test_int_array Sys.argv.(2)
289289+290290+ | "string" when Array.length Sys.argv = 3 ->
291291+ test_string_array Sys.argv.(2)
292292+293293+ | "float" when Array.length Sys.argv = 3 ->
294294+ test_float_array Sys.argv.(2)
295295+296296+ | "empty" when Array.length Sys.argv = 3 ->
297297+ test_empty_array Sys.argv.(2)
298298+299299+ | "objects" when Array.length Sys.argv = 3 ->
300300+ test_object_array Sys.argv.(2)
301301+302302+ | "nested" when Array.length Sys.argv = 3 ->
303303+ test_nested_arrays Sys.argv.(2)
304304+305305+ | "type-mismatch" when Array.length Sys.argv = 3 ->
306306+ test_type_mismatch Sys.argv.(2)
307307+308308+ | "bool" when Array.length Sys.argv = 3 ->
309309+ test_bool_array Sys.argv.(2)
310310+311311+ | "nullable" when Array.length Sys.argv = 3 ->
312312+ test_nullable_array Sys.argv.(2)
313313+314314+ | "encode" when Array.length Sys.argv = 2 ->
315315+ test_encode_arrays ()
316316+317317+ | _ ->
318318+ prerr_endline usage;
319319+ prerr_endline "Commands:";
320320+ prerr_endline " int <file> - Test int array";
321321+ prerr_endline " string <file> - Test string array";
322322+ prerr_endline " float <file> - Test float array";
323323+ prerr_endline " empty <file> - Test empty array";
324324+ prerr_endline " objects <file> - Test array of objects";
325325+ prerr_endline " nested <file> - Test nested arrays";
326326+ prerr_endline " type-mismatch <file> - Test type mismatch error";
327327+ prerr_endline " bool <file> - Test bool array";
328328+ prerr_endline " nullable <file> - Test array with nulls";
329329+ prerr_endline " encode - Test encoding arrays";
330330+ exit 1
+193
tests/bin/test_complex.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test complex nested types with Yamlt *)
77+88+(* Helper to read file *)
99+let read_file path =
1010+ let ic = open_in path in
1111+ let len = in_channel_length ic in
1212+ let s = really_input_string ic len in
1313+ close_in ic;
1414+ s
1515+1616+(* Helper to show results *)
1717+let show_result label = function
1818+ | Ok v -> Printf.printf "%s: %s\n" label v
1919+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2020+2121+let show_result_both label json_result yaml_result =
2222+ Printf.printf "JSON: ";
2323+ show_result label json_result;
2424+ Printf.printf "YAML: ";
2525+ show_result label yaml_result
2626+2727+(* Test: Deeply nested objects *)
2828+let test_deep_nesting file =
2929+ let module M = struct
3030+ type level3 = { value: int }
3131+ type level2 = { data: level3 }
3232+ type level1 = { nested: level2 }
3333+ type root = { top: level1 }
3434+3535+ let level3_codec =
3636+ Jsont.Object.map ~kind:"Level3" (fun value -> { value })
3737+ |> Jsont.Object.mem "value" Jsont.int ~enc:(fun l -> l.value)
3838+ |> Jsont.Object.finish
3939+4040+ let level2_codec =
4141+ Jsont.Object.map ~kind:"Level2" (fun data -> { data })
4242+ |> Jsont.Object.mem "data" level3_codec ~enc:(fun l -> l.data)
4343+ |> Jsont.Object.finish
4444+4545+ let level1_codec =
4646+ Jsont.Object.map ~kind:"Level1" (fun nested -> { nested })
4747+ |> Jsont.Object.mem "nested" level2_codec ~enc:(fun l -> l.nested)
4848+ |> Jsont.Object.finish
4949+5050+ let root_codec =
5151+ Jsont.Object.map ~kind:"Root" (fun top -> { top })
5252+ |> Jsont.Object.mem "top" level1_codec ~enc:(fun r -> r.top)
5353+ |> Jsont.Object.finish
5454+5555+ let show r = Printf.sprintf "depth=4, value=%d" r.top.nested.data.value
5656+ end in
5757+5858+ let yaml = read_file file in
5959+ let json = read_file (file ^ ".json") in
6060+ let json_result = Jsont_bytesrw.decode_string M.root_codec json in
6161+ let yaml_result = Yamlt.decode_string M.root_codec yaml in
6262+6363+ show_result_both "deep_nesting"
6464+ (Result.map M.show json_result)
6565+ (Result.map M.show yaml_result)
6666+6767+(* Test: Array of objects with nested arrays *)
6868+let test_mixed_structure file =
6969+ let module M = struct
7070+ type item = { id: int; tags: string array }
7171+ type collection = { name: string; items: item array }
7272+7373+ let item_codec =
7474+ Jsont.Object.map ~kind:"Item" (fun id tags -> { id; tags })
7575+ |> Jsont.Object.mem "id" Jsont.int ~enc:(fun i -> i.id)
7676+ |> Jsont.Object.mem "tags" (Jsont.array Jsont.string) ~enc:(fun i -> i.tags)
7777+ |> Jsont.Object.finish
7878+7979+ let collection_codec =
8080+ Jsont.Object.map ~kind:"Collection" (fun name items -> { name; items })
8181+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
8282+ |> Jsont.Object.mem "items" (Jsont.array item_codec) ~enc:(fun c -> c.items)
8383+ |> Jsont.Object.finish
8484+8585+ let show c =
8686+ let total_tags = Stdlib.Array.fold_left (fun acc item ->
8787+ acc + Stdlib.Array.length item.tags) 0 c.items in
8888+ Printf.sprintf "name=%S, items=%d, total_tags=%d"
8989+ c.name (Stdlib.Array.length c.items) total_tags
9090+ end in
9191+9292+ let yaml = read_file file in
9393+ let json = read_file (file ^ ".json") in
9494+ let json_result = Jsont_bytesrw.decode_string M.collection_codec json in
9595+ let yaml_result = Yamlt.decode_string M.collection_codec yaml in
9696+9797+ show_result_both "mixed_structure"
9898+ (Result.map M.show json_result)
9999+ (Result.map M.show yaml_result)
100100+101101+(* Test: Complex optional and nullable combinations *)
102102+let test_complex_optional file =
103103+ let module M = struct
104104+ type config = {
105105+ host: string;
106106+ port: int option;
107107+ ssl: bool option;
108108+ cert_path: string option;
109109+ fallback_hosts: string array option;
110110+ }
111111+112112+ let config_codec =
113113+ Jsont.Object.map ~kind:"Config"
114114+ (fun host port ssl cert_path fallback_hosts ->
115115+ { host; port; ssl; cert_path; fallback_hosts })
116116+ |> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
117117+ |> Jsont.Object.opt_mem "port" Jsont.int ~enc:(fun c -> c.port)
118118+ |> Jsont.Object.opt_mem "ssl" Jsont.bool ~enc:(fun c -> c.ssl)
119119+ |> Jsont.Object.opt_mem "cert_path" Jsont.string ~enc:(fun c -> c.cert_path)
120120+ |> Jsont.Object.opt_mem "fallback_hosts" (Jsont.array Jsont.string)
121121+ ~enc:(fun c -> c.fallback_hosts)
122122+ |> Jsont.Object.finish
123123+124124+ let show c =
125125+ let port_str = match c.port with None -> "None" | Some p -> string_of_int p in
126126+ let ssl_str = match c.ssl with None -> "None" | Some b -> string_of_bool b in
127127+ let fallbacks = match c.fallback_hosts with
128128+ | None -> 0
129129+ | Some arr -> Stdlib.Array.length arr in
130130+ Printf.sprintf "host=%S, port=%s, ssl=%s, fallbacks=%d"
131131+ c.host port_str ssl_str fallbacks
132132+ end in
133133+134134+ let yaml = read_file file in
135135+ let json = read_file (file ^ ".json") in
136136+ let json_result = Jsont_bytesrw.decode_string M.config_codec json in
137137+ let yaml_result = Yamlt.decode_string M.config_codec yaml in
138138+139139+ show_result_both "complex_optional"
140140+ (Result.map M.show json_result)
141141+ (Result.map M.show yaml_result)
142142+143143+(* Test: Heterogeneous data via any type *)
144144+let test_heterogeneous file =
145145+ let module M = struct
146146+ type data = { mixed: Jsont.json array }
147147+148148+ let data_codec =
149149+ Jsont.Object.map ~kind:"Data" (fun mixed -> { mixed })
150150+ |> Jsont.Object.mem "mixed" (Jsont.array (Jsont.any ())) ~enc:(fun d -> d.mixed)
151151+ |> Jsont.Object.finish
152152+153153+ let show d = Printf.sprintf "items=%d" (Stdlib.Array.length d.mixed)
154154+ end in
155155+156156+ let yaml = read_file file in
157157+ let json = read_file (file ^ ".json") in
158158+ let json_result = Jsont_bytesrw.decode_string M.data_codec json in
159159+ let yaml_result = Yamlt.decode_string M.data_codec yaml in
160160+161161+ show_result_both "heterogeneous"
162162+ (Result.map M.show json_result)
163163+ (Result.map M.show yaml_result)
164164+165165+let () =
166166+ let usage = "Usage: test_complex <command> [args...]" in
167167+168168+ if Stdlib.Array.length Sys.argv < 2 then begin
169169+ prerr_endline usage;
170170+ exit 1
171171+ end;
172172+173173+ match Sys.argv.(1) with
174174+ | "deep-nesting" when Stdlib.Array.length Sys.argv = 3 ->
175175+ test_deep_nesting Sys.argv.(2)
176176+177177+ | "mixed-structure" when Stdlib.Array.length Sys.argv = 3 ->
178178+ test_mixed_structure Sys.argv.(2)
179179+180180+ | "complex-optional" when Stdlib.Array.length Sys.argv = 3 ->
181181+ test_complex_optional Sys.argv.(2)
182182+183183+ | "heterogeneous" when Stdlib.Array.length Sys.argv = 3 ->
184184+ test_heterogeneous Sys.argv.(2)
185185+186186+ | _ ->
187187+ prerr_endline usage;
188188+ prerr_endline "Commands:";
189189+ prerr_endline " deep-nesting <file> - Test deeply nested objects";
190190+ prerr_endline " mixed-structure <file> - Test arrays of objects with nested arrays";
191191+ prerr_endline " complex-optional <file> - Test complex optional/nullable combinations";
192192+ prerr_endline " heterogeneous <file> - Test heterogeneous data via any type";
193193+ exit 1
+212
tests/bin/test_edge.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test edge cases with Yamlt *)
77+88+(* Helper to read file *)
99+let read_file path =
1010+ let ic = open_in path in
1111+ let len = in_channel_length ic in
1212+ let s = really_input_string ic len in
1313+ close_in ic;
1414+ s
1515+1616+(* Helper to show results *)
1717+let show_result label = function
1818+ | Ok v -> Printf.printf "%s: %s\n" label v
1919+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2020+2121+let show_result_both label json_result yaml_result =
2222+ Printf.printf "JSON: ";
2323+ show_result label json_result;
2424+ Printf.printf "YAML: ";
2525+ show_result label yaml_result
2626+2727+(* Test: Very large numbers *)
2828+let test_large_numbers file =
2929+ let module M = struct
3030+ type numbers = { large_int: float; large_float: float; small_float: float }
3131+3232+ let numbers_codec =
3333+ Jsont.Object.map ~kind:"Numbers" (fun large_int large_float small_float ->
3434+ { large_int; large_float; small_float })
3535+ |> Jsont.Object.mem "large_int" Jsont.number ~enc:(fun n -> n.large_int)
3636+ |> Jsont.Object.mem "large_float" Jsont.number ~enc:(fun n -> n.large_float)
3737+ |> Jsont.Object.mem "small_float" Jsont.number ~enc:(fun n -> n.small_float)
3838+ |> Jsont.Object.finish
3939+4040+ let show n =
4141+ Printf.sprintf "large_int=%.0f, large_float=%e, small_float=%e"
4242+ n.large_int n.large_float n.small_float
4343+ end in
4444+4545+ let yaml = read_file file in
4646+ let json = read_file (file ^ ".json") in
4747+ let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
4848+ let yaml_result = Yamlt.decode_string M.numbers_codec yaml in
4949+5050+ show_result_both "large_numbers"
5151+ (Result.map M.show json_result)
5252+ (Result.map M.show yaml_result)
5353+5454+(* Test: Special characters in strings *)
5555+let test_special_chars file =
5656+ let module M = struct
5757+ type text = { content: string }
5858+5959+ let text_codec =
6060+ Jsont.Object.map ~kind:"Text" (fun content -> { content })
6161+ |> Jsont.Object.mem "content" Jsont.string ~enc:(fun t -> t.content)
6262+ |> Jsont.Object.finish
6363+6464+ let show t =
6565+ Printf.sprintf "length=%d, contains_newline=%b, contains_tab=%b"
6666+ (String.length t.content)
6767+ (String.contains t.content '\n')
6868+ (String.contains t.content '\t')
6969+ end in
7070+7171+ let yaml = read_file file in
7272+ let json = read_file (file ^ ".json") in
7373+ let json_result = Jsont_bytesrw.decode_string M.text_codec json in
7474+ let yaml_result = Yamlt.decode_string M.text_codec yaml in
7575+7676+ show_result_both "special_chars"
7777+ (Result.map M.show json_result)
7878+ (Result.map M.show yaml_result)
7979+8080+(* Test: Unicode strings *)
8181+let test_unicode file =
8282+ let module M = struct
8383+ type text = { emoji: string; chinese: string; rtl: string }
8484+8585+ let text_codec =
8686+ Jsont.Object.map ~kind:"Text" (fun emoji chinese rtl -> { emoji; chinese; rtl })
8787+ |> Jsont.Object.mem "emoji" Jsont.string ~enc:(fun t -> t.emoji)
8888+ |> Jsont.Object.mem "chinese" Jsont.string ~enc:(fun t -> t.chinese)
8989+ |> Jsont.Object.mem "rtl" Jsont.string ~enc:(fun t -> t.rtl)
9090+ |> Jsont.Object.finish
9191+9292+ let show t =
9393+ Printf.sprintf "emoji=%S, chinese=%S, rtl=%S" t.emoji t.chinese t.rtl
9494+ end in
9595+9696+ let yaml = read_file file in
9797+ let json = read_file (file ^ ".json") in
9898+ let json_result = Jsont_bytesrw.decode_string M.text_codec json in
9999+ let yaml_result = Yamlt.decode_string M.text_codec yaml in
100100+101101+ show_result_both "unicode"
102102+ (Result.map M.show json_result)
103103+ (Result.map M.show yaml_result)
104104+105105+(* Test: Empty collections *)
106106+let test_empty_collections file =
107107+ let module M = struct
108108+ type data = { empty_array: int array; empty_object_array: unit array }
109109+110110+ let data_codec =
111111+ Jsont.Object.map ~kind:"Data" (fun empty_array empty_object_array ->
112112+ { empty_array; empty_object_array })
113113+ |> Jsont.Object.mem "empty_array" (Jsont.array Jsont.int) ~enc:(fun d -> d.empty_array)
114114+ |> Jsont.Object.mem "empty_object_array" (Jsont.array (Jsont.null ())) ~enc:(fun d -> d.empty_object_array)
115115+ |> Jsont.Object.finish
116116+117117+ let show d =
118118+ Printf.sprintf "empty_array_len=%d, empty_object_array_len=%d"
119119+ (Stdlib.Array.length d.empty_array)
120120+ (Stdlib.Array.length d.empty_object_array)
121121+ end in
122122+123123+ let yaml = read_file file in
124124+ let json = read_file (file ^ ".json") in
125125+ let json_result = Jsont_bytesrw.decode_string M.data_codec json in
126126+ let yaml_result = Yamlt.decode_string M.data_codec yaml in
127127+128128+ show_result_both "empty_collections"
129129+ (Result.map M.show json_result)
130130+ (Result.map M.show yaml_result)
131131+132132+(* Test: Key names with special characters *)
133133+let test_special_keys file =
134134+ let module M = struct
135135+ let show j =
136136+ match Jsont.Json.decode (Jsont.any ()) j with
137137+ | Ok (Jsont.Object _) -> "valid_object"
138138+ | Ok _ -> "not_object"
139139+ | Error _ -> "decode_error"
140140+ end in
141141+142142+ let yaml = read_file file in
143143+ let json = read_file (file ^ ".json") in
144144+ let json_result = Jsont_bytesrw.decode_string (Jsont.any ()) json in
145145+ let yaml_result = Yamlt.decode_string (Jsont.any ()) yaml in
146146+147147+ show_result_both "special_keys"
148148+ (Result.map M.show json_result)
149149+ (Result.map M.show yaml_result)
150150+151151+(* Test: Single-element arrays *)
152152+let test_single_element file =
153153+ let module M = struct
154154+ type data = { single: int array }
155155+156156+ let data_codec =
157157+ Jsont.Object.map ~kind:"Data" (fun single -> { single })
158158+ |> Jsont.Object.mem "single" (Jsont.array Jsont.int) ~enc:(fun d -> d.single)
159159+ |> Jsont.Object.finish
160160+161161+ let show d =
162162+ Printf.sprintf "length=%d, value=%d"
163163+ (Stdlib.Array.length d.single)
164164+ (if Stdlib.Array.length d.single > 0 then d.single.(0) else 0)
165165+ end in
166166+167167+ let yaml = read_file file in
168168+ let json = read_file (file ^ ".json") in
169169+ let json_result = Jsont_bytesrw.decode_string M.data_codec json in
170170+ let yaml_result = Yamlt.decode_string M.data_codec yaml in
171171+172172+ show_result_both "single_element"
173173+ (Result.map M.show json_result)
174174+ (Result.map M.show yaml_result)
175175+176176+let () =
177177+ let usage = "Usage: test_edge <command> [args...]" in
178178+179179+ if Stdlib.Array.length Sys.argv < 2 then begin
180180+ prerr_endline usage;
181181+ exit 1
182182+ end;
183183+184184+ match Sys.argv.(1) with
185185+ | "large-numbers" when Stdlib.Array.length Sys.argv = 3 ->
186186+ test_large_numbers Sys.argv.(2)
187187+188188+ | "special-chars" when Stdlib.Array.length Sys.argv = 3 ->
189189+ test_special_chars Sys.argv.(2)
190190+191191+ | "unicode" when Stdlib.Array.length Sys.argv = 3 ->
192192+ test_unicode Sys.argv.(2)
193193+194194+ | "empty-collections" when Stdlib.Array.length Sys.argv = 3 ->
195195+ test_empty_collections Sys.argv.(2)
196196+197197+ | "special-keys" when Stdlib.Array.length Sys.argv = 3 ->
198198+ test_special_keys Sys.argv.(2)
199199+200200+ | "single-element" when Stdlib.Array.length Sys.argv = 3 ->
201201+ test_single_element Sys.argv.(2)
202202+203203+ | _ ->
204204+ prerr_endline usage;
205205+ prerr_endline "Commands:";
206206+ prerr_endline " large-numbers <file> - Test very large numbers";
207207+ prerr_endline " special-chars <file> - Test special characters in strings";
208208+ prerr_endline " unicode <file> - Test Unicode strings";
209209+ prerr_endline " empty-collections <file> - Test empty collections";
210210+ prerr_endline " special-keys <file> - Test special characters in keys";
211211+ prerr_endline " single-element <file> - Test single-element arrays";
212212+ exit 1
+254
tests/bin/test_formats.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test format-specific features with Yamlt *)
77+88+(* Helper to read file *)
99+let read_file path =
1010+ let ic = open_in path in
1111+ let len = in_channel_length ic in
1212+ let s = really_input_string ic len in
1313+ close_in ic;
1414+ s
1515+1616+(* Helper to show results *)
1717+let show_result label = function
1818+ | Ok v -> Printf.printf "%s: %s\n" label v
1919+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2020+2121+let show_result_both label json_result yaml_result =
2222+ Printf.printf "JSON: ";
2323+ show_result label json_result;
2424+ Printf.printf "YAML: ";
2525+ show_result label yaml_result
2626+2727+(* Test: Multi-line strings - literal style *)
2828+let test_literal_string file =
2929+ let module M = struct
3030+ type text = { content: string }
3131+3232+ let text_codec =
3333+ Jsont.Object.map ~kind:"Text" (fun content -> { content })
3434+ |> Jsont.Object.mem "content" Jsont.string ~enc:(fun t -> t.content)
3535+ |> Jsont.Object.finish
3636+3737+ let show t =
3838+ Printf.sprintf "lines=%d, length=%d"
3939+ (List.length (String.split_on_char '\n' t.content))
4040+ (String.length t.content)
4141+ end in
4242+4343+ let yaml = read_file file in
4444+ let json = read_file (file ^ ".json") in
4545+ let json_result = Jsont_bytesrw.decode_string M.text_codec json in
4646+ let yaml_result = Yamlt.decode_string M.text_codec yaml in
4747+4848+ show_result_both "literal_string"
4949+ (Result.map M.show json_result)
5050+ (Result.map M.show yaml_result)
5151+5252+(* Test: Multi-line strings - folded style *)
5353+let test_folded_string file =
5454+ let module M = struct
5555+ type text = { content: string }
5656+5757+ let text_codec =
5858+ Jsont.Object.map ~kind:"Text" (fun content -> { content })
5959+ |> Jsont.Object.mem "content" Jsont.string ~enc:(fun t -> t.content)
6060+ |> Jsont.Object.finish
6161+6262+ let show t =
6363+ Printf.sprintf "length=%d, newlines=%d"
6464+ (String.length t.content)
6565+ (List.length (List.filter (fun c -> c = '\n')
6666+ (List.init (String.length t.content) (String.get t.content))))
6767+ end in
6868+6969+ let yaml = read_file file in
7070+ let json = read_file (file ^ ".json") in
7171+ let json_result = Jsont_bytesrw.decode_string M.text_codec json in
7272+ let yaml_result = Yamlt.decode_string M.text_codec yaml in
7373+7474+ show_result_both "folded_string"
7575+ (Result.map M.show json_result)
7676+ (Result.map M.show yaml_result)
7777+7878+(* Test: Number formats - hex, octal, binary *)
7979+let test_number_formats file =
8080+ let module M = struct
8181+ type numbers = { hex: float; octal: float; binary: float }
8282+8383+ let numbers_codec =
8484+ Jsont.Object.map ~kind:"Numbers" (fun hex octal binary -> { hex; octal; binary })
8585+ |> Jsont.Object.mem "hex" Jsont.number ~enc:(fun n -> n.hex)
8686+ |> Jsont.Object.mem "octal" Jsont.number ~enc:(fun n -> n.octal)
8787+ |> Jsont.Object.mem "binary" Jsont.number ~enc:(fun n -> n.binary)
8888+ |> Jsont.Object.finish
8989+9090+ let show n =
9191+ Printf.sprintf "hex=%.0f, octal=%.0f, binary=%.0f" n.hex n.octal n.binary
9292+ end in
9393+9494+ let yaml = read_file file in
9595+ let json = read_file (file ^ ".json") in
9696+ let json_result = Jsont_bytesrw.decode_string M.numbers_codec json in
9797+ let yaml_result = Yamlt.decode_string M.numbers_codec yaml in
9898+9999+ show_result_both "number_formats"
100100+ (Result.map M.show json_result)
101101+ (Result.map M.show yaml_result)
102102+103103+(* Test: Block vs Flow style encoding *)
104104+let test_encode_styles () =
105105+ let module M = struct
106106+ type data = {
107107+ name: string;
108108+ values: int array;
109109+ nested: nested_data;
110110+ }
111111+ and nested_data = {
112112+ enabled: bool;
113113+ count: int;
114114+ }
115115+116116+ let nested_codec =
117117+ Jsont.Object.map ~kind:"Nested" (fun enabled count -> { enabled; count })
118118+ |> Jsont.Object.mem "enabled" Jsont.bool ~enc:(fun n -> n.enabled)
119119+ |> Jsont.Object.mem "count" Jsont.int ~enc:(fun n -> n.count)
120120+ |> Jsont.Object.finish
121121+122122+ let data_codec =
123123+ Jsont.Object.map ~kind:"Data" (fun name values nested -> { name; values; nested })
124124+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun d -> d.name)
125125+ |> Jsont.Object.mem "values" (Jsont.array Jsont.int) ~enc:(fun d -> d.values)
126126+ |> Jsont.Object.mem "nested" nested_codec ~enc:(fun d -> d.nested)
127127+ |> Jsont.Object.finish
128128+ end in
129129+130130+ let data = {
131131+ M.name = "test";
132132+ values = [|1; 2; 3|];
133133+ nested = { enabled = true; count = 5 };
134134+ } in
135135+136136+ (* Encode to YAML Block style *)
137137+ (match Yamlt.encode_string ~format:Yamlt.Block M.data_codec data with
138138+ | Ok s -> Printf.printf "YAML Block:\n%s\n" s
139139+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
140140+141141+ (* Encode to YAML Flow style *)
142142+ (match Yamlt.encode_string ~format:Yamlt.Flow M.data_codec data with
143143+ | Ok s -> Printf.printf "YAML Flow:\n%s\n" s
144144+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
145145+146146+(* Test: Comments in YAML (should be ignored) *)
147147+let test_comments file =
148148+ let module M = struct
149149+ type config = { host: string; port: int; debug: bool }
150150+151151+ let config_codec =
152152+ Jsont.Object.map ~kind:"Config" (fun host port debug -> { host; port; debug })
153153+ |> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
154154+ |> Jsont.Object.mem "port" Jsont.int ~enc:(fun c -> c.port)
155155+ |> Jsont.Object.mem "debug" Jsont.bool ~enc:(fun c -> c.debug)
156156+ |> Jsont.Object.finish
157157+158158+ let show c =
159159+ Printf.sprintf "host=%S, port=%d, debug=%b" c.host c.port c.debug
160160+ end in
161161+162162+ let yaml = read_file file in
163163+ let yaml_result = Yamlt.decode_string M.config_codec yaml in
164164+165165+ match yaml_result with
166166+ | Ok v -> Printf.printf "YAML (with comments): %s\n" (M.show v)
167167+ | Error e -> Printf.printf "YAML ERROR: %s\n" e
168168+169169+(* Test: Empty documents and null documents *)
170170+let test_empty_document file =
171171+ let module M = struct
172172+ type wrapper = { value: string option }
173173+174174+ let wrapper_codec =
175175+ Jsont.Object.map ~kind:"Wrapper" (fun value -> { value })
176176+ |> Jsont.Object.mem "value" (Jsont.some Jsont.string) ~enc:(fun w -> w.value)
177177+ |> Jsont.Object.finish
178178+179179+ let show w =
180180+ match w.value with
181181+ | None -> "value=None"
182182+ | Some s -> Printf.sprintf "value=Some(%S)" s
183183+ end in
184184+185185+ let yaml = read_file file in
186186+ let json = read_file (file ^ ".json") in
187187+ let json_result = Jsont_bytesrw.decode_string M.wrapper_codec json in
188188+ let yaml_result = Yamlt.decode_string M.wrapper_codec yaml in
189189+190190+ show_result_both "empty_document"
191191+ (Result.map M.show json_result)
192192+ (Result.map M.show yaml_result)
193193+194194+(* Test: Explicit typing with tags (if supported) *)
195195+let test_explicit_tags file =
196196+ let module M = struct
197197+ type value_holder = { data: string }
198198+199199+ let value_codec =
200200+ Jsont.Object.map ~kind:"ValueHolder" (fun data -> { data })
201201+ |> Jsont.Object.mem "data" Jsont.string ~enc:(fun v -> v.data)
202202+ |> Jsont.Object.finish
203203+204204+ let show v = Printf.sprintf "data=%S" v.data
205205+ end in
206206+207207+ let yaml = read_file file in
208208+ let yaml_result = Yamlt.decode_string M.value_codec yaml in
209209+210210+ match yaml_result with
211211+ | Ok v -> Printf.printf "YAML (with tags): %s\n" (M.show v)
212212+ | Error e -> Printf.printf "YAML ERROR: %s\n" e
213213+214214+let () =
215215+ let usage = "Usage: test_formats <command> [args...]" in
216216+217217+ if Stdlib.Array.length Sys.argv < 2 then begin
218218+ prerr_endline usage;
219219+ exit 1
220220+ end;
221221+222222+ match Sys.argv.(1) with
223223+ | "literal" when Stdlib.Array.length Sys.argv = 3 ->
224224+ test_literal_string Sys.argv.(2)
225225+226226+ | "folded" when Stdlib.Array.length Sys.argv = 3 ->
227227+ test_folded_string Sys.argv.(2)
228228+229229+ | "number-formats" when Stdlib.Array.length Sys.argv = 3 ->
230230+ test_number_formats Sys.argv.(2)
231231+232232+ | "encode-styles" when Stdlib.Array.length Sys.argv = 2 ->
233233+ test_encode_styles ()
234234+235235+ | "comments" when Stdlib.Array.length Sys.argv = 3 ->
236236+ test_comments Sys.argv.(2)
237237+238238+ | "empty-doc" when Stdlib.Array.length Sys.argv = 3 ->
239239+ test_empty_document Sys.argv.(2)
240240+241241+ | "explicit-tags" when Stdlib.Array.length Sys.argv = 3 ->
242242+ test_explicit_tags Sys.argv.(2)
243243+244244+ | _ ->
245245+ prerr_endline usage;
246246+ prerr_endline "Commands:";
247247+ prerr_endline " literal <file> - Test literal multi-line strings";
248248+ prerr_endline " folded <file> - Test folded multi-line strings";
249249+ prerr_endline " number-formats <file> - Test hex/octal/binary number formats";
250250+ prerr_endline " encode-styles - Test block vs flow encoding";
251251+ prerr_endline " comments <file> - Test YAML with comments";
252252+ prerr_endline " empty-doc <file> - Test empty documents";
253253+ prerr_endline " explicit-tags <file> - Test explicit type tags";
254254+ exit 1
+33
tests/bin/test_null_complete.ml
···11+let () =
22+ Printf.printf "=== Test 1: Jsont.option with YAML null ===\n";
33+ let yaml1 = "value: null" in
44+ let codec1 =
55+ let open Jsont in
66+ Object.map ~kind:"Test" (fun v -> v)
77+ |> Object.mem "value" (option string) ~enc:(fun v -> v)
88+ |> Object.finish
99+ in
1010+ (match Yamlt.decode_string codec1 yaml1 with
1111+ | Ok v -> Printf.printf "Result: %s\n" (match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
1212+ | Error e -> Printf.printf "Error: %s\n" e);
1313+1414+ Printf.printf "\n=== Test 2: Jsont.option with YAML string ===\n";
1515+ (match Yamlt.decode_string codec1 "value: hello" with
1616+ | Ok v -> Printf.printf "Result: %s\n" (match v with None -> "None" | Some s -> "Some(" ^ s ^ ")")
1717+ | Error e -> Printf.printf "Error: %s\n" e);
1818+1919+ Printf.printf "\n=== Test 3: Jsont.string with YAML null (should error) ===\n";
2020+ let codec2 =
2121+ let open Jsont in
2222+ Object.map ~kind:"Test" (fun v -> v)
2323+ |> Object.mem "value" string ~enc:(fun v -> v)
2424+ |> Object.finish
2525+ in
2626+ (match Yamlt.decode_string codec2 "value: null" with
2727+ | Ok v -> Printf.printf "Result: %s\n" v
2828+ | Error e -> Printf.printf "Error (expected): %s\n" e);
2929+3030+ Printf.printf "\n=== Test 4: Jsont.string with YAML string ===\n";
3131+ (match Yamlt.decode_string codec2 "value: hello" with
3232+ | Ok v -> Printf.printf "Result: %s\n" v
3333+ | Error e -> Printf.printf "Error: %s\n" e)
+30
tests/bin/test_null_fix.ml
···11+open Jsont
22+33+let () =
44+ let module M = struct
55+ type data = { value: string option }
66+77+ let data_codec =
88+ Jsont.Object.map ~kind:"Data" (fun value -> { value })
99+ |> Jsont.Object.mem "value" (Jsont.option Jsont.string) ~enc:(fun d -> d.value)
1010+ |> Jsont.Object.finish
1111+ end in
1212+1313+ let yaml_null = "value: null" in
1414+1515+ Printf.printf "Testing YAML null handling with Jsont.option Jsont.string:\n\n";
1616+1717+ match Yamlt.decode_string M.data_codec yaml_null with
1818+ | Ok data ->
1919+ (match data.M.value with
2020+ | None -> Printf.printf "YAML: value=None (CORRECT)\n"
2121+ | Some s -> Printf.printf "YAML: value=Some(%S) (BUG!)\n" s)
2222+ | Error e -> Printf.printf "YAML ERROR: %s\n" e;
2323+2424+ let json_null = "{\"value\": null}" in
2525+ match Jsont_bytesrw.decode_string M.data_codec json_null with
2626+ | Ok data ->
2727+ (match data.M.value with
2828+ | None -> Printf.printf "JSON: value=None (CORRECT)\n"
2929+ | Some s -> Printf.printf "JSON: value=Some(%S) (BUG!)\n" s)
3030+ | Error e -> Printf.printf "JSON ERROR: %s\n" e
+302
tests/bin/test_objects.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test object codec functionality with Yamlt *)
77+88+(* Helper to read file *)
99+let read_file path =
1010+ let ic = open_in path in
1111+ let len = in_channel_length ic in
1212+ let s = really_input_string ic len in
1313+ close_in ic;
1414+ s
1515+1616+(* Helper to show results *)
1717+let show_result label = function
1818+ | Ok v -> Printf.printf "%s: %s\n" label v
1919+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2020+2121+let show_result_both label json_result yaml_result =
2222+ Printf.printf "JSON: ";
2323+ show_result label json_result;
2424+ Printf.printf "YAML: ";
2525+ show_result label yaml_result
2626+2727+(* Test: Simple object with required fields *)
2828+let test_simple_object file =
2929+ let module M = struct
3030+ type person = { name: string; age: int }
3131+3232+ let person_codec =
3333+ Jsont.Object.map ~kind:"Person" (fun name age -> { name; age })
3434+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
3535+ |> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
3636+ |> Jsont.Object.finish
3737+3838+ let show p = Printf.sprintf "{name=%S; age=%d}" p.name p.age
3939+ end in
4040+4141+ let yaml = read_file file in
4242+ let json = read_file (file ^ ".json") in
4343+ let json_result = Jsont_bytesrw.decode_string M.person_codec json in
4444+ let yaml_result = Yamlt.decode_string M.person_codec yaml in
4545+4646+ show_result_both "person"
4747+ (Result.map M.show json_result)
4848+ (Result.map M.show yaml_result)
4949+5050+(* Test: Object with optional fields *)
5151+let test_optional_fields file =
5252+ let module M = struct
5353+ type config = { host: string; port: int option; debug: bool option }
5454+5555+ let config_codec =
5656+ Jsont.Object.map ~kind:"Config"
5757+ (fun host port debug -> { host; port; debug })
5858+ |> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
5959+ |> Jsont.Object.opt_mem "port" Jsont.int ~enc:(fun c -> c.port)
6060+ |> Jsont.Object.opt_mem "debug" Jsont.bool ~enc:(fun c -> c.debug)
6161+ |> Jsont.Object.finish
6262+6363+ let show c =
6464+ Printf.sprintf "{host=%S; port=%s; debug=%s}"
6565+ c.host
6666+ (match c.port with None -> "None" | Some p -> Printf.sprintf "Some %d" p)
6767+ (match c.debug with None -> "None" | Some b -> Printf.sprintf "Some %b" b)
6868+ end in
6969+7070+ let yaml = read_file file in
7171+ let json = read_file (file ^ ".json") in
7272+ let json_result = Jsont_bytesrw.decode_string M.config_codec json in
7373+ let yaml_result = Yamlt.decode_string M.config_codec yaml in
7474+7575+ show_result_both "config"
7676+ (Result.map M.show json_result)
7777+ (Result.map M.show yaml_result)
7878+7979+(* Test: Object with default values *)
8080+let test_default_values file =
8181+ let module M = struct
8282+ type settings = { timeout: int; retries: int; verbose: bool }
8383+8484+ let settings_codec =
8585+ Jsont.Object.map ~kind:"Settings"
8686+ (fun timeout retries verbose -> { timeout; retries; verbose })
8787+ |> Jsont.Object.mem "timeout" Jsont.int ~enc:(fun s -> s.timeout) ~dec_absent:30
8888+ |> Jsont.Object.mem "retries" Jsont.int ~enc:(fun s -> s.retries) ~dec_absent:3
8989+ |> Jsont.Object.mem "verbose" Jsont.bool ~enc:(fun s -> s.verbose) ~dec_absent:false
9090+ |> Jsont.Object.finish
9191+9292+ let show s =
9393+ Printf.sprintf "{timeout=%d; retries=%d; verbose=%b}"
9494+ s.timeout s.retries s.verbose
9595+ end in
9696+9797+ let yaml = read_file file in
9898+ let json = read_file (file ^ ".json") in
9999+ let json_result = Jsont_bytesrw.decode_string M.settings_codec json in
100100+ let yaml_result = Yamlt.decode_string M.settings_codec yaml in
101101+102102+ show_result_both "settings"
103103+ (Result.map M.show json_result)
104104+ (Result.map M.show yaml_result)
105105+106106+(* Test: Nested objects *)
107107+let test_nested_objects file =
108108+ let module M = struct
109109+ type address = { street: string; city: string; zip: string }
110110+ type employee = { name: string; address: address }
111111+112112+ let address_codec =
113113+ Jsont.Object.map ~kind:"Address"
114114+ (fun street city zip -> { street; city; zip })
115115+ |> Jsont.Object.mem "street" Jsont.string ~enc:(fun a -> a.street)
116116+ |> Jsont.Object.mem "city" Jsont.string ~enc:(fun a -> a.city)
117117+ |> Jsont.Object.mem "zip" Jsont.string ~enc:(fun a -> a.zip)
118118+ |> Jsont.Object.finish
119119+120120+ let employee_codec =
121121+ Jsont.Object.map ~kind:"Employee"
122122+ (fun name address -> { name; address })
123123+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun e -> e.name)
124124+ |> Jsont.Object.mem "address" address_codec ~enc:(fun e -> e.address)
125125+ |> Jsont.Object.finish
126126+127127+ let show e =
128128+ Printf.sprintf "{name=%S; address={street=%S; city=%S; zip=%S}}"
129129+ e.name e.address.street e.address.city e.address.zip
130130+ end in
131131+132132+ let yaml = read_file file in
133133+ let json = read_file (file ^ ".json") in
134134+ let json_result = Jsont_bytesrw.decode_string M.employee_codec json in
135135+ let yaml_result = Yamlt.decode_string M.employee_codec yaml in
136136+137137+ show_result_both "employee"
138138+ (Result.map M.show json_result)
139139+ (Result.map M.show yaml_result)
140140+141141+(* Test: Unknown member handling - error *)
142142+let test_unknown_members_error file =
143143+ let module M = struct
144144+ type strict = { name: string }
145145+146146+ let strict_codec =
147147+ Jsont.Object.map ~kind:"Strict" (fun name -> { name })
148148+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun s -> s.name)
149149+ |> Jsont.Object.finish
150150+ end in
151151+152152+ let yaml = read_file file in
153153+ let result = Yamlt.decode_string M.strict_codec yaml in
154154+ match result with
155155+ | Ok _ -> Printf.printf "Unexpected success\n"
156156+ | Error e -> Printf.printf "Expected error: %s\n" e
157157+158158+(* Test: Unknown member handling - keep *)
159159+let test_unknown_members_keep file =
160160+ let module M = struct
161161+ type flexible = { name: string; extra: Jsont.json }
162162+163163+ let flexible_codec =
164164+ Jsont.Object.map ~kind:"Flexible" (fun name extra -> { name; extra })
165165+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun f -> f.name)
166166+ |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:(fun f -> f.extra)
167167+ |> Jsont.Object.finish
168168+169169+ let show f =
170170+ Printf.sprintf "{name=%S; has_extra=true}" f.name
171171+ end in
172172+173173+ let yaml = read_file file in
174174+ let json = read_file (file ^ ".json") in
175175+ let json_result = Jsont_bytesrw.decode_string M.flexible_codec json in
176176+ let yaml_result = Yamlt.decode_string M.flexible_codec yaml in
177177+178178+ show_result_both "flexible"
179179+ (Result.map M.show json_result)
180180+ (Result.map M.show yaml_result)
181181+182182+(* Test: Object cases (discriminated unions) - simplified version *)
183183+let test_object_cases file =
184184+ let module M = struct
185185+ type circle = { type_: string; radius: float }
186186+187187+ let circle_codec =
188188+ Jsont.Object.map ~kind:"Circle" (fun type_ radius -> { type_; radius })
189189+ |> Jsont.Object.mem "type" Jsont.string ~enc:(fun c -> c.type_)
190190+ |> Jsont.Object.mem "radius" Jsont.number ~enc:(fun c -> c.radius)
191191+ |> Jsont.Object.finish
192192+193193+ let show c =
194194+ Printf.sprintf "Circle{radius=%.2f}" c.radius
195195+ end in
196196+197197+ let yaml = read_file file in
198198+ let json = read_file (file ^ ".json") in
199199+ let json_result = Jsont_bytesrw.decode_string M.circle_codec json in
200200+ let yaml_result = Yamlt.decode_string M.circle_codec yaml in
201201+202202+ show_result_both "shape"
203203+ (Result.map M.show json_result)
204204+ (Result.map M.show yaml_result)
205205+206206+(* Test: Missing required field error *)
207207+let test_missing_required file =
208208+ let module M = struct
209209+ type required = { name: string; age: int }
210210+211211+ let required_codec =
212212+ Jsont.Object.map ~kind:"Required" (fun name age -> { name; age })
213213+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun r -> r.name)
214214+ |> Jsont.Object.mem "age" Jsont.int ~enc:(fun r -> r.age)
215215+ |> Jsont.Object.finish
216216+ end in
217217+218218+ let yaml = read_file file in
219219+ let result = Yamlt.decode_string M.required_codec yaml in
220220+ match result with
221221+ | Ok _ -> Printf.printf "Unexpected success\n"
222222+ | Error e -> Printf.printf "Expected error: %s\n" e
223223+224224+(* Test: Encoding objects to different formats *)
225225+let test_encode_object () =
226226+ let module M = struct
227227+ type person = { name: string; age: int; active: bool }
228228+229229+ let person_codec =
230230+ Jsont.Object.map ~kind:"Person" (fun name age active -> { name; age; active })
231231+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.name)
232232+ |> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
233233+ |> Jsont.Object.mem "active" Jsont.bool ~enc:(fun p -> p.active)
234234+ |> Jsont.Object.finish
235235+ end in
236236+237237+ let person = M.{ name = "Alice"; age = 30; active = true } in
238238+239239+ (* Encode to JSON *)
240240+ (match Jsont_bytesrw.encode_string M.person_codec person with
241241+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
242242+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
243243+244244+ (* Encode to YAML Block *)
245245+ (match Yamlt.encode_string ~format:Yamlt.Block M.person_codec person with
246246+ | Ok s -> Printf.printf "YAML Block:\n%s" s
247247+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
248248+249249+ (* Encode to YAML Flow *)
250250+ (match Yamlt.encode_string ~format:Yamlt.Flow M.person_codec person with
251251+ | Ok s -> Printf.printf "YAML Flow: %s" s
252252+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
253253+254254+let () =
255255+ let usage = "Usage: test_objects <command> [args...]" in
256256+257257+ if Stdlib.Array.length Sys.argv < 2 then begin
258258+ prerr_endline usage;
259259+ exit 1
260260+ end;
261261+262262+ match Sys.argv.(1) with
263263+ | "simple" when Stdlib.Array.length Sys.argv = 3 ->
264264+ test_simple_object Sys.argv.(2)
265265+266266+ | "optional" when Stdlib.Array.length Sys.argv = 3 ->
267267+ test_optional_fields Sys.argv.(2)
268268+269269+ | "defaults" when Stdlib.Array.length Sys.argv = 3 ->
270270+ test_default_values Sys.argv.(2)
271271+272272+ | "nested" when Stdlib.Array.length Sys.argv = 3 ->
273273+ test_nested_objects Sys.argv.(2)
274274+275275+ | "unknown-error" when Stdlib.Array.length Sys.argv = 3 ->
276276+ test_unknown_members_error Sys.argv.(2)
277277+278278+ | "unknown-keep" when Stdlib.Array.length Sys.argv = 3 ->
279279+ test_unknown_members_keep Sys.argv.(2)
280280+281281+ | "cases" when Stdlib.Array.length Sys.argv = 3 ->
282282+ test_object_cases Sys.argv.(2)
283283+284284+ | "missing-required" when Stdlib.Array.length Sys.argv = 3 ->
285285+ test_missing_required Sys.argv.(2)
286286+287287+ | "encode" when Stdlib.Array.length Sys.argv = 2 ->
288288+ test_encode_object ()
289289+290290+ | _ ->
291291+ prerr_endline usage;
292292+ prerr_endline "Commands:";
293293+ prerr_endline " simple <file> - Test simple object";
294294+ prerr_endline " optional <file> - Test optional fields";
295295+ prerr_endline " defaults <file> - Test default values";
296296+ prerr_endline " nested <file> - Test nested objects";
297297+ prerr_endline " unknown-error <file> - Test unknown member error";
298298+ prerr_endline " unknown-keep <file> - Test keeping unknown members";
299299+ prerr_endline " cases <file> - Test object cases (unions)";
300300+ prerr_endline " missing-required <file> - Test missing required field error";
301301+ prerr_endline " encode - Test encoding objects";
302302+ exit 1
+16
tests/bin/test_opt_array.ml
···11+let () =
22+ let codec =
33+ Jsont.Object.map ~kind:"Test" (fun arr -> arr)
44+ |> Jsont.Object.opt_mem "values" (Jsont.array Jsont.string) ~enc:(fun arr -> arr)
55+ |> Jsont.Object.finish
66+ in
77+88+ let yaml = "values: [a, b, c]" in
99+1010+ Printf.printf "Testing optional array field:\n";
1111+ match Yamlt.decode_string codec yaml with
1212+ | Ok arr ->
1313+ (match arr with
1414+ | None -> Printf.printf "Result: None\n"
1515+ | Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
1616+ | Error e -> Printf.printf "Error: %s\n" e
+197
tests/bin/test_roundtrip.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test roundtrip encoding/decoding with Yamlt *)
77+88+(* Test: Roundtrip scalars *)
99+let test_scalar_roundtrip () =
1010+ let module M = struct
1111+ type data = { s: string; n: float; b: bool; nul: unit }
1212+1313+ let data_codec =
1414+ Jsont.Object.map ~kind:"Data" (fun s n b nul -> { s; n; b; nul })
1515+ |> Jsont.Object.mem "s" Jsont.string ~enc:(fun d -> d.s)
1616+ |> Jsont.Object.mem "n" Jsont.number ~enc:(fun d -> d.n)
1717+ |> Jsont.Object.mem "b" Jsont.bool ~enc:(fun d -> d.b)
1818+ |> Jsont.Object.mem "nul" (Jsont.null ()) ~enc:(fun d -> d.nul)
1919+ |> Jsont.Object.finish
2020+2121+ let equal d1 d2 =
2222+ d1.s = d2.s && d1.n = d2.n && d1.b = d2.b && d1.nul = d2.nul
2323+ end in
2424+2525+ let original = { M.s = "hello"; n = 42.5; b = true; nul = () } in
2626+2727+ (* JSON roundtrip *)
2828+ let json_encoded = Jsont_bytesrw.encode_string M.data_codec original in
2929+ let json_decoded = Result.bind json_encoded (Jsont_bytesrw.decode_string M.data_codec) in
3030+ (match json_decoded with
3131+ | Ok decoded when M.equal original decoded -> Printf.printf "JSON roundtrip: PASS\n"
3232+ | Ok _ -> Printf.printf "JSON roundtrip: FAIL (data mismatch)\n"
3333+ | Error e -> Printf.printf "JSON roundtrip: FAIL (%s)\n" e);
3434+3535+ (* YAML Block roundtrip *)
3636+ let yaml_block_encoded = Yamlt.encode_string ~format:Yamlt.Block M.data_codec original in
3737+ let yaml_block_decoded = Result.bind yaml_block_encoded (Yamlt.decode_string M.data_codec) in
3838+ (match yaml_block_decoded with
3939+ | Ok decoded when M.equal original decoded -> Printf.printf "YAML Block roundtrip: PASS\n"
4040+ | Ok _ -> Printf.printf "YAML Block roundtrip: FAIL (data mismatch)\n"
4141+ | Error e -> Printf.printf "YAML Block roundtrip: FAIL (%s)\n" e);
4242+4343+ (* YAML Flow roundtrip *)
4444+ let yaml_flow_encoded = Yamlt.encode_string ~format:Yamlt.Flow M.data_codec original in
4545+ let yaml_flow_decoded = Result.bind yaml_flow_encoded (Yamlt.decode_string M.data_codec) in
4646+ (match yaml_flow_decoded with
4747+ | Ok decoded when M.equal original decoded -> Printf.printf "YAML Flow roundtrip: PASS\n"
4848+ | Ok _ -> Printf.printf "YAML Flow roundtrip: FAIL (data mismatch)\n"
4949+ | Error e -> Printf.printf "YAML Flow roundtrip: FAIL (%s)\n" e)
5050+5151+(* Test: Roundtrip arrays *)
5252+let test_array_roundtrip () =
5353+ let module M = struct
5454+ type data = { items: int array; nested: float array array }
5555+5656+ let data_codec =
5757+ Jsont.Object.map ~kind:"Data" (fun items nested -> { items; nested })
5858+ |> Jsont.Object.mem "items" (Jsont.array Jsont.int) ~enc:(fun d -> d.items)
5959+ |> Jsont.Object.mem "nested" (Jsont.array (Jsont.array Jsont.number)) ~enc:(fun d -> d.nested)
6060+ |> Jsont.Object.finish
6161+6262+ let equal d1 d2 =
6363+ d1.items = d2.items && d1.nested = d2.nested
6464+ end in
6565+6666+ let original = { M.items = [|1; 2; 3; 4; 5|]; nested = [|[|1.0; 2.0|]; [|3.0; 4.0|]|] } in
6767+6868+ (* JSON roundtrip *)
6969+ let json_result = Result.bind
7070+ (Jsont_bytesrw.encode_string M.data_codec original)
7171+ (Jsont_bytesrw.decode_string M.data_codec) in
7272+ (match json_result with
7373+ | Ok decoded when M.equal original decoded -> Printf.printf "JSON array roundtrip: PASS\n"
7474+ | Ok _ -> Printf.printf "JSON array roundtrip: FAIL (data mismatch)\n"
7575+ | Error e -> Printf.printf "JSON array roundtrip: FAIL (%s)\n" e);
7676+7777+ (* YAML roundtrip *)
7878+ let yaml_result = Result.bind
7979+ (Yamlt.encode_string M.data_codec original)
8080+ (Yamlt.decode_string M.data_codec) in
8181+ (match yaml_result with
8282+ | Ok decoded when M.equal original decoded -> Printf.printf "YAML array roundtrip: PASS\n"
8383+ | Ok _ -> Printf.printf "YAML array roundtrip: FAIL (data mismatch)\n"
8484+ | Error e -> Printf.printf "YAML array roundtrip: FAIL (%s)\n" e)
8585+8686+(* Test: Roundtrip objects *)
8787+let test_object_roundtrip () =
8888+ let module M = struct
8989+ type person = { p_name: string; age: int; active: bool }
9090+ type company = { c_name: string; employees: person array }
9191+9292+ let person_codec =
9393+ Jsont.Object.map ~kind:"Person" (fun p_name age active -> { p_name; age; active })
9494+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun p -> p.p_name)
9595+ |> Jsont.Object.mem "age" Jsont.int ~enc:(fun p -> p.age)
9696+ |> Jsont.Object.mem "active" Jsont.bool ~enc:(fun p -> p.active)
9797+ |> Jsont.Object.finish
9898+9999+ let company_codec =
100100+ Jsont.Object.map ~kind:"Company" (fun c_name employees -> { c_name; employees })
101101+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.c_name)
102102+ |> Jsont.Object.mem "employees" (Jsont.array person_codec) ~enc:(fun c -> c.employees)
103103+ |> Jsont.Object.finish
104104+105105+ let person_equal p1 p2 =
106106+ p1.p_name = p2.p_name && p1.age = p2.age && p1.active = p2.active
107107+108108+ let equal c1 c2 =
109109+ c1.c_name = c2.c_name &&
110110+ Stdlib.Array.length c1.employees = Stdlib.Array.length c2.employees &&
111111+ Stdlib.Array.for_all2 person_equal c1.employees c2.employees
112112+ end in
113113+114114+ let original = {
115115+ M.c_name = "Acme Corp";
116116+ employees = [|
117117+ { p_name = "Alice"; age = 30; active = true };
118118+ { p_name = "Bob"; age = 25; active = false };
119119+ |]
120120+ } in
121121+122122+ (* JSON roundtrip *)
123123+ let json_result = Result.bind
124124+ (Jsont_bytesrw.encode_string M.company_codec original)
125125+ (Jsont_bytesrw.decode_string M.company_codec) in
126126+ (match json_result with
127127+ | Ok decoded when M.equal original decoded -> Printf.printf "JSON object roundtrip: PASS\n"
128128+ | Ok _ -> Printf.printf "JSON object roundtrip: FAIL (data mismatch)\n"
129129+ | Error e -> Printf.printf "JSON object roundtrip: FAIL (%s)\n" e);
130130+131131+ (* YAML roundtrip *)
132132+ let yaml_result = Result.bind
133133+ (Yamlt.encode_string M.company_codec original)
134134+ (Yamlt.decode_string M.company_codec) in
135135+ (match yaml_result with
136136+ | Ok decoded when M.equal original decoded -> Printf.printf "YAML object roundtrip: PASS\n"
137137+ | Ok _ -> Printf.printf "YAML object roundtrip: FAIL (data mismatch)\n"
138138+ | Error e -> Printf.printf "YAML object roundtrip: FAIL (%s)\n" e)
139139+140140+(* Test: Roundtrip with optionals *)
141141+let test_optional_roundtrip () =
142142+ let module M = struct
143143+ type data = { required: string; optional: int option; nullable: string option }
144144+145145+ let data_codec =
146146+ Jsont.Object.map ~kind:"Data" (fun required optional nullable -> { required; optional; nullable })
147147+ |> Jsont.Object.mem "required" Jsont.string ~enc:(fun d -> d.required)
148148+ |> Jsont.Object.opt_mem "optional" Jsont.int ~enc:(fun d -> d.optional)
149149+ |> Jsont.Object.mem "nullable" (Jsont.some Jsont.string) ~enc:(fun d -> d.nullable)
150150+ |> Jsont.Object.finish
151151+152152+ let equal d1 d2 =
153153+ d1.required = d2.required && d1.optional = d2.optional && d1.nullable = d2.nullable
154154+ end in
155155+156156+ let original = { M.required = "test"; optional = Some 42; nullable = None } in
157157+158158+ (* JSON roundtrip *)
159159+ let json_result = Result.bind
160160+ (Jsont_bytesrw.encode_string M.data_codec original)
161161+ (Jsont_bytesrw.decode_string M.data_codec) in
162162+ (match json_result with
163163+ | Ok decoded when M.equal original decoded -> Printf.printf "JSON optional roundtrip: PASS\n"
164164+ | Ok _ -> Printf.printf "JSON optional roundtrip: FAIL (data mismatch)\n"
165165+ | Error e -> Printf.printf "JSON optional roundtrip: FAIL (%s)\n" e);
166166+167167+ (* YAML roundtrip *)
168168+ let yaml_result = Result.bind
169169+ (Yamlt.encode_string M.data_codec original)
170170+ (Yamlt.decode_string M.data_codec) in
171171+ (match yaml_result with
172172+ | Ok decoded when M.equal original decoded -> Printf.printf "YAML optional roundtrip: PASS\n"
173173+ | Ok _ -> Printf.printf "YAML optional roundtrip: FAIL (data mismatch)\n"
174174+ | Error e -> Printf.printf "YAML optional roundtrip: FAIL (%s)\n" e)
175175+176176+let () =
177177+ let usage = "Usage: test_roundtrip <command>" in
178178+179179+ if Stdlib.Array.length Sys.argv < 2 then begin
180180+ prerr_endline usage;
181181+ exit 1
182182+ end;
183183+184184+ match Sys.argv.(1) with
185185+ | "scalar" -> test_scalar_roundtrip ()
186186+ | "array" -> test_array_roundtrip ()
187187+ | "object" -> test_object_roundtrip ()
188188+ | "optional" -> test_optional_roundtrip ()
189189+190190+ | _ ->
191191+ prerr_endline usage;
192192+ prerr_endline "Commands:";
193193+ prerr_endline " scalar - Test scalar roundtrip";
194194+ prerr_endline " array - Test array roundtrip";
195195+ prerr_endline " object - Test object roundtrip";
196196+ prerr_endline " optional - Test optional fields roundtrip";
197197+ exit 1
+304
tests/bin/test_scalars.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2024 The yamlrw programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Test scalar type resolution with Yamlt codec *)
77+88+(* Helper to read file *)
99+let read_file path =
1010+ let ic = open_in path in
1111+ let len = in_channel_length ic in
1212+ let s = really_input_string ic len in
1313+ close_in ic;
1414+ s
1515+1616+(* Helper to show results *)
1717+let show_result label = function
1818+ | Ok v -> Printf.printf "%s: %s\n" label v
1919+ | Error e -> Printf.printf "%s: ERROR: %s\n" label e
2020+2121+let show_result_json label json_result yaml_result =
2222+ Printf.printf "JSON %s\n" label;
2323+ show_result " decode" json_result;
2424+ Printf.printf "YAML %s\n" label;
2525+ show_result " decode" yaml_result
2626+2727+(* Test: Decode null values with different type expectations *)
2828+let test_null_resolution file =
2929+ let yaml = read_file file in
3030+3131+ (* Define a simple object codec with nullable field *)
3232+ let null_codec =
3333+ Jsont.Object.map ~kind:"NullTest" (fun n -> n)
3434+ |> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
3535+ |> Jsont.Object.finish
3636+ in
3737+3838+ (* Try decoding as null *)
3939+ let result = Yamlt.decode_string null_codec yaml in
4040+ show_result "null_codec" (Result.map (fun () -> "null") result)
4141+4242+(* Test: Boolean type-directed resolution *)
4343+let test_bool_resolution file =
4444+ let yaml = read_file file in
4545+ let json = read_file (file ^ ".json") in
4646+4747+ (* Codec expecting bool *)
4848+ let bool_codec =
4949+ Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
5050+ |> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
5151+ |> Jsont.Object.finish
5252+ in
5353+5454+ (* Codec expecting string *)
5555+ let string_codec =
5656+ Jsont.Object.map ~kind:"StringTest" (fun s -> s)
5757+ |> Jsont.Object.mem "value" Jsont.string ~enc:(fun s -> s)
5858+ |> Jsont.Object.finish
5959+ in
6060+6161+ Printf.printf "=== Bool Codec ===\n";
6262+ let json_result = Jsont_bytesrw.decode_string bool_codec json in
6363+ let yaml_result = Yamlt.decode_string bool_codec yaml in
6464+ show_result_json "bool_codec"
6565+ (Result.map (Printf.sprintf "%b") json_result)
6666+ (Result.map (Printf.sprintf "%b") yaml_result);
6767+6868+ Printf.printf "\n=== String Codec ===\n";
6969+ let json_result = Jsont_bytesrw.decode_string string_codec json in
7070+ let yaml_result = Yamlt.decode_string string_codec yaml in
7171+ show_result_json "string_codec"
7272+ (Result.map (Printf.sprintf "%S") json_result)
7373+ (Result.map (Printf.sprintf "%S") yaml_result)
7474+7575+(* Test: Number resolution *)
7676+let test_number_resolution file =
7777+ let yaml = read_file file in
7878+ let json = read_file (file ^ ".json") in
7979+8080+ let number_codec =
8181+ Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
8282+ |> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
8383+ |> Jsont.Object.finish
8484+ in
8585+8686+ let json_result = Jsont_bytesrw.decode_string number_codec json in
8787+ let yaml_result = Yamlt.decode_string number_codec yaml in
8888+8989+ show_result_json "number_codec"
9090+ (Result.map (Printf.sprintf "%.17g") json_result)
9191+ (Result.map (Printf.sprintf "%.17g") yaml_result)
9292+9393+(* Test: String resolution preserves everything *)
9494+let test_string_resolution file =
9595+ let yaml = read_file file in
9696+ let json = read_file (file ^ ".json") in
9797+9898+ let string_codec =
9999+ Jsont.Object.map ~kind:"StringTest" (fun s -> s)
100100+ |> Jsont.Object.mem "value" Jsont.string ~enc:(fun s -> s)
101101+ |> Jsont.Object.finish
102102+ in
103103+104104+ let json_result = Jsont_bytesrw.decode_string string_codec json in
105105+ let yaml_result = Yamlt.decode_string string_codec yaml in
106106+107107+ show_result_json "string_codec"
108108+ (Result.map (Printf.sprintf "%S") json_result)
109109+ (Result.map (Printf.sprintf "%S") yaml_result)
110110+111111+(* Test: Special float values *)
112112+let test_special_floats file =
113113+ let yaml = read_file file in
114114+115115+ let number_codec =
116116+ Jsont.Object.map ~kind:"SpecialFloat" (fun n -> n)
117117+ |> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
118118+ |> Jsont.Object.finish
119119+ in
120120+121121+ let result = Yamlt.decode_string number_codec yaml in
122122+ match result with
123123+ | Ok f ->
124124+ if Float.is_nan f then
125125+ Printf.printf "value: NaN\n"
126126+ else if f = Float.infinity then
127127+ Printf.printf "value: +Infinity\n"
128128+ else if f = Float.neg_infinity then
129129+ Printf.printf "value: -Infinity\n"
130130+ else
131131+ Printf.printf "value: %.17g\n" f
132132+ | Error e ->
133133+ Printf.printf "ERROR: %s\n" e
134134+135135+(* Test: Type mismatch errors *)
136136+let test_type_mismatch file expected_type =
137137+ let yaml = read_file file in
138138+139139+ match expected_type with
140140+ | "bool" ->
141141+ let codec =
142142+ Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
143143+ |> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
144144+ |> Jsont.Object.finish
145145+ in
146146+ let result = Yamlt.decode_string codec yaml in
147147+ (match result with
148148+ | Ok _ -> Printf.printf "Unexpected success\n"
149149+ | Error e -> Printf.printf "Expected error: %s\n" e)
150150+ | "number" ->
151151+ let codec =
152152+ Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
153153+ |> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
154154+ |> Jsont.Object.finish
155155+ in
156156+ let result = Yamlt.decode_string codec yaml in
157157+ (match result with
158158+ | Ok _ -> Printf.printf "Unexpected success\n"
159159+ | Error e -> Printf.printf "Expected error: %s\n" e)
160160+ | "null" ->
161161+ let codec =
162162+ Jsont.Object.map ~kind:"NullTest" (fun n -> n)
163163+ |> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
164164+ |> Jsont.Object.finish
165165+ in
166166+ let result = Yamlt.decode_string codec yaml in
167167+ (match result with
168168+ | Ok _ -> Printf.printf "Unexpected success\n"
169169+ | Error e -> Printf.printf "Expected error: %s\n" e)
170170+ | _ -> failwith "unknown type"
171171+172172+(* Test: Decode with Jsont.json to see auto-resolution *)
173173+let test_any_resolution file =
174174+ let yaml = read_file file in
175175+ let json = read_file (file ^ ".json") in
176176+177177+ let any_codec =
178178+ Jsont.Object.map ~kind:"AnyTest" (fun v -> v)
179179+ |> Jsont.Object.mem "value" Jsont.json ~enc:(fun v -> v)
180180+ |> Jsont.Object.finish
181181+ in
182182+183183+ let json_result = Jsont_bytesrw.decode_string any_codec json in
184184+ let yaml_result = Yamlt.decode_string any_codec yaml in
185185+186186+ (* Just show that it decoded successfully *)
187187+ show_result_json "any_codec"
188188+ (Result.map (fun _ -> "decoded") json_result)
189189+ (Result.map (fun _ -> "decoded") yaml_result)
190190+191191+(* Test: Encoding to different formats *)
192192+let test_encode_formats value_type value =
193193+ match value_type with
194194+ | "bool" ->
195195+ let codec =
196196+ Jsont.Object.map ~kind:"BoolTest" (fun b -> b)
197197+ |> Jsont.Object.mem "value" Jsont.bool ~enc:(fun b -> b)
198198+ |> Jsont.Object.finish
199199+ in
200200+ let v = bool_of_string value in
201201+ (match Jsont_bytesrw.encode_string codec v with
202202+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
203203+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
204204+ (match Yamlt.encode_string ~format:Yamlt.Block codec v with
205205+ | Ok s -> Printf.printf "YAML Block:\n%s" s
206206+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
207207+ (match Yamlt.encode_string ~format:Yamlt.Flow codec v with
208208+ | Ok s -> Printf.printf "YAML Flow: %s" s
209209+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
210210+ | "number" ->
211211+ let codec =
212212+ Jsont.Object.map ~kind:"NumberTest" (fun n -> n)
213213+ |> Jsont.Object.mem "value" Jsont.number ~enc:(fun n -> n)
214214+ |> Jsont.Object.finish
215215+ in
216216+ let v = float_of_string value in
217217+ (match Jsont_bytesrw.encode_string codec v with
218218+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
219219+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
220220+ (match Yamlt.encode_string ~format:Yamlt.Block codec v with
221221+ | Ok s -> Printf.printf "YAML Block:\n%s" s
222222+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
223223+ (match Yamlt.encode_string ~format:Yamlt.Flow codec v with
224224+ | Ok s -> Printf.printf "YAML Flow: %s" s
225225+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
226226+ | "string" ->
227227+ let codec =
228228+ Jsont.Object.map ~kind:"StringTest" (fun s -> s)
229229+ |> Jsont.Object.mem "value" Jsont.string ~enc:(fun s -> s)
230230+ |> Jsont.Object.finish
231231+ in
232232+ let v = value in
233233+ (match Jsont_bytesrw.encode_string codec v with
234234+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
235235+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
236236+ (match Yamlt.encode_string ~format:Yamlt.Block codec v with
237237+ | Ok s -> Printf.printf "YAML Block:\n%s" s
238238+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
239239+ (match Yamlt.encode_string ~format:Yamlt.Flow codec v with
240240+ | Ok s -> Printf.printf "YAML Flow: %s" s
241241+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
242242+ | "null" ->
243243+ let codec =
244244+ Jsont.Object.map ~kind:"NullTest" (fun n -> n)
245245+ |> Jsont.Object.mem "value" (Jsont.null ()) ~enc:(fun n -> n)
246246+ |> Jsont.Object.finish
247247+ in
248248+ let v = () in
249249+ (match Jsont_bytesrw.encode_string codec v with
250250+ | Ok s -> Printf.printf "JSON: %s\n" (String.trim s)
251251+ | Error e -> Printf.printf "JSON ERROR: %s\n" e);
252252+ (match Yamlt.encode_string ~format:Yamlt.Block codec v with
253253+ | Ok s -> Printf.printf "YAML Block:\n%s" s
254254+ | Error e -> Printf.printf "YAML Block ERROR: %s\n" e);
255255+ (match Yamlt.encode_string ~format:Yamlt.Flow codec v with
256256+ | Ok s -> Printf.printf "YAML Flow: %s" s
257257+ | Error e -> Printf.printf "YAML Flow ERROR: %s\n" e)
258258+ | _ -> failwith "unknown type"
259259+260260+let () =
261261+ let usage = "Usage: test_scalars <command> [args...]" in
262262+263263+ if Stdlib.Array.length Sys.argv < 2 then begin
264264+ prerr_endline usage;
265265+ exit 1
266266+ end;
267267+268268+ match Sys.argv.(1) with
269269+ | "null" when Array.length Sys.argv = 3 ->
270270+ test_null_resolution Sys.argv.(2)
271271+272272+ | "bool" when Array.length Sys.argv = 3 ->
273273+ test_bool_resolution Sys.argv.(2)
274274+275275+ | "number" when Array.length Sys.argv = 3 ->
276276+ test_number_resolution Sys.argv.(2)
277277+278278+ | "string" when Array.length Sys.argv = 3 ->
279279+ test_string_resolution Sys.argv.(2)
280280+281281+ | "special-float" when Array.length Sys.argv = 3 ->
282282+ test_special_floats Sys.argv.(2)
283283+284284+ | "type-mismatch" when Array.length Sys.argv = 4 ->
285285+ test_type_mismatch Sys.argv.(2) Sys.argv.(3)
286286+287287+ | "any" when Array.length Sys.argv = 3 ->
288288+ test_any_resolution Sys.argv.(2)
289289+290290+ | "encode" when Array.length Sys.argv = 4 ->
291291+ test_encode_formats Sys.argv.(2) Sys.argv.(3)
292292+293293+ | _ ->
294294+ prerr_endline usage;
295295+ prerr_endline "Commands:";
296296+ prerr_endline " null <file> - Test null resolution";
297297+ prerr_endline " bool <file> - Test bool vs string resolution";
298298+ prerr_endline " number <file> - Test number resolution";
299299+ prerr_endline " string <file> - Test string resolution";
300300+ prerr_endline " special-float <file> - Test .inf, .nan, etc.";
301301+ prerr_endline " type-mismatch <file> <type> - Test error on type mismatch";
302302+ prerr_endline " any <file> - Test Jsont.any auto-resolution";
303303+ prerr_endline " encode <type> <value> - Test encoding to JSON/YAML";
304304+ exit 1
+32
tests/bin/test_some_vs_option.ml
···11+let () =
22+ (* Using Jsont.some like opt_mem does *)
33+ let codec1 =
44+ Jsont.Object.map ~kind:"Test" (fun arr -> arr)
55+ |> Jsont.Object.mem "values" (Jsont.some (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
66+ |> Jsont.Object.finish
77+ in
88+99+ let yaml = "values: [a, b, c]" in
1010+1111+ Printf.printf "Test 1: Jsont.some (Jsont.array) - like opt_mem:\n";
1212+ (match Yamlt.decode_string codec1 yaml with
1313+ | Ok arr ->
1414+ (match arr with
1515+ | None -> Printf.printf "Result: None\n"
1616+ | Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
1717+ | Error e -> Printf.printf "Error: %s\n" e);
1818+1919+ (* Using Jsont.option *)
2020+ let codec2 =
2121+ Jsont.Object.map ~kind:"Test" (fun arr -> arr)
2222+ |> Jsont.Object.mem "values" (Jsont.option (Jsont.array Jsont.string)) ~enc:(fun arr -> arr)
2323+ |> Jsont.Object.finish
2424+ in
2525+2626+ Printf.printf "\nTest 2: Jsont.option (Jsont.array):\n";
2727+ (match Yamlt.decode_string codec2 yaml with
2828+ | Ok arr ->
2929+ (match arr with
3030+ | None -> Printf.printf "Result: None\n"
3131+ | Some a -> Printf.printf "Result: Some([%d items])\n" (Array.length a))
3232+ | Error e -> Printf.printf "Error: %s\n" e)
+143
tests/cram/arrays_codec.t
···11+Array Codec Tests with Yamlt
22+===============================
33+44+This test suite validates array encoding/decoding with Jsont codecs in YAML,
55+including homogeneous type checking and nested structures.
66+77+Setup
88+-----
99+1010+================================================================================
1111+HOMOGENEOUS ARRAYS
1212+================================================================================
1313+1414+Integer arrays
1515+1616+ $ test_arrays int ../data/arrays/int_array.yml
1717+ JSON: int_array: [1; 2; 3; 4; 5]
1818+ YAML: int_array: [1; 2; 3; 4; 5]
1919+2020+String arrays
2121+2222+ $ test_arrays string ../data/arrays/string_array.yml
2323+ JSON: string_array: ["apple"; "banana"; "cherry"]
2424+ YAML: string_array: ["apple"; "banana"; "cherry"]
2525+2626+Float/Number arrays
2727+2828+ $ test_arrays float ../data/arrays/float_array.yml
2929+ JSON: float_array: [1.50; 2.70; 3.14; 0.50]
3030+ YAML: float_array: [1.50; 2.70; 3.14; 0.50]
3131+3232+Boolean arrays
3333+3434+ $ test_arrays bool ../data/arrays/bool_array.yml
3535+ JSON: bool_array: [true; false; true; true; false]
3636+ YAML: bool_array: [true; false; true; true; false]
3737+3838+================================================================================
3939+EMPTY ARRAYS
4040+================================================================================
4141+4242+Empty arrays work correctly
4343+4444+ $ test_arrays empty ../data/arrays/empty_array.yml
4545+ JSON: empty_array: length=0
4646+ YAML: empty_array: length=0
4747+4848+================================================================================
4949+ARRAYS OF OBJECTS
5050+================================================================================
5151+5252+Arrays containing objects
5353+5454+ $ test_arrays objects ../data/arrays/object_array.yml
5555+ JSON: object_array: [{Alice,30}; {Bob,25}; {Charlie,35}]
5656+ YAML: object_array: [{Alice,30}; {Bob,25}; {Charlie,35}]
5757+5858+================================================================================
5959+NESTED ARRAYS
6060+================================================================================
6161+6262+Arrays containing arrays (matrices)
6363+6464+ $ test_arrays nested ../data/arrays/nested_array.yml
6565+ JSON: nested_arrays: [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
6666+ YAML: nested_arrays: [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
6767+6868+================================================================================
6969+NULLABLE ARRAYS
7070+================================================================================
7171+7272+Arrays with null elements
7373+7474+ $ test_arrays nullable ../data/arrays/nullable_array.yml
7575+ JSON: nullable_array: ERROR: Expected string but found null
7676+ File "-", line 1, characters 21-22:
7777+ File "-", line 1, characters 21-22: at index 1 of
7878+ File "-", line 1, characters 11-22: array<string>
7979+ File "-": in member values of
8080+ File "-", line 1, characters 0-22: Nullable object
8181+ YAML: nullable_array: ["hello"; "null"; "world"; "null"; "test"]
8282+8383+================================================================================
8484+ERROR HANDLING
8585+================================================================================
8686+8787+Type mismatch in array element
8888+8989+ $ test_arrays type-mismatch ../data/arrays/type_mismatch.yml
9090+ Expected error: String "not-a-number" does not parse to OCaml int value
9191+ File "-":
9292+ at index 2 of
9393+ File "-": array<OCaml int>
9494+ File "-": in member values of
9595+ File "-": Numbers object
9696+9797+================================================================================
9898+ENCODING ARRAYS
9999+================================================================================
100100+101101+Encode arrays to JSON and YAML formats
102102+103103+ $ test_arrays encode
104104+ JSON: {"numbers":[1,2,3,4,5],"strings":["hello","world"]}
105105+ YAML Block:
106106+ numbers:
107107+ - 1.0
108108+ - 2.0
109109+ - 3.0
110110+ - 4.0
111111+ - 5.0
112112+ strings:
113113+ - hello
114114+ - world
115115+ YAML Flow: {numbers: [1.0, 2.0, 3.0, 4.0, 5.0]strings, [hello, world]}
116116+117117+================================================================================
118118+NEGATIVE TESTS - Wrong File Types
119119+================================================================================
120120+121121+Attempting to decode an object file with an array codec should fail
122122+123123+ $ test_arrays int ../data/objects/simple.yml
124124+ JSON: int_array: ERROR: Missing member values in Numbers object
125125+ File "-", line 1, characters 0-28:
126126+ YAML: int_array: ERROR: Missing member values in Numbers object
127127+ File "-":
128128+129129+Attempting to decode a scalar file with an array codec should fail
130130+131131+ $ test_arrays string ../data/scalars/string_plain.yml
132132+ JSON: string_array: ERROR: Missing member items in Tags object
133133+ File "-", line 1, characters 0-24:
134134+ YAML: string_array: ERROR: Missing member items in Tags object
135135+ File "-":
136136+137137+Attempting to decode int array with string array codec should fail
138138+139139+ $ test_arrays string ../data/arrays/int_array.yml
140140+ JSON: string_array: ERROR: Missing member items in Tags object
141141+ File "-", line 1, characters 0-27:
142142+ YAML: string_array: ERROR: Missing member items in Tags object
143143+ File "-":
+74
tests/cram/complex_codec.t
···11+Complex Nested Types Tests with Yamlt
22+======================================
33+44+This test suite validates complex nested structures combining objects, arrays,
55+and various levels of nesting.
66+77+================================================================================
88+DEEPLY NESTED OBJECTS
99+================================================================================
1010+1111+Handle deeply nested object structures
1212+1313+ $ test_complex deep-nesting ../data/complex/deep_nesting.yml
1414+ JSON: deep_nesting: depth=4, value=42
1515+ YAML: deep_nesting: depth=4, value=42
1616+1717+================================================================================
1818+MIXED STRUCTURES
1919+================================================================================
2020+2121+Arrays of objects containing arrays
2222+2323+ $ test_complex mixed-structure ../data/complex/mixed_structure.yml
2424+ JSON: mixed_structure: name="products", items=3, total_tags=6
2525+ YAML: mixed_structure: name="products", items=3, total_tags=6
2626+2727+================================================================================
2828+COMPLEX OPTIONAL COMBINATIONS
2929+================================================================================
3030+3131+Multiple optional fields with different combinations
3232+3333+ $ test_complex complex-optional ../data/complex/complex_optional.yml
3434+ JSON: complex_optional: host="example.com", port=443, ssl=true, fallbacks=2
3535+ YAML: complex_optional: ERROR: Expected array<string> but found sequence
3636+ File "-":
3737+ File "-": in member fallback_hosts of
3838+ File "-": Config object
3939+4040+================================================================================
4141+HETEROGENEOUS DATA
4242+================================================================================
4343+4444+Mixed types in arrays using any type
4545+4646+ $ test_complex heterogeneous ../data/complex/heterogeneous.yml
4747+ JSON: heterogeneous: ERROR: Expected one of but found number
4848+ File "-", line 1, characters 11-12:
4949+ File "-", line 1, characters 11-12: at index 0 of
5050+ File "-", line 1, characters 10-12: array<one of >
5151+ File "-": in member mixed of
5252+ File "-", line 1, characters 0-12: Data object
5353+ YAML: heterogeneous: ERROR: Expected one of but found number
5454+ File "-":
5555+ at index 0 of
5656+ File "-": array<one of >
5757+ File "-": in member mixed of
5858+ File "-": Data object
5959+6060+================================================================================
6161+NEGATIVE TESTS - Structure Mismatch
6262+================================================================================
6363+6464+Using deeply nested data with flat codec should fail
6565+6666+ $ test_complex mixed-structure ../data/complex/deep_nesting.yml
6767+ JSON: mixed_structure: ERROR: Missing members in Collection object:
6868+ items
6969+ name
7070+ File "-", line 1, characters 0-44:
7171+ YAML: mixed_structure: ERROR: Missing members in Collection object:
7272+ items
7373+ name
7474+ File "-":
···11+# Configuration file with comments
22+host: localhost # The server host
33+port: 8080 # The server port
44+# Enable debug mode for development
55+debug: true