+11
doc/config.json
+11
doc/config.json
+133
src/jsont_pointer.ml
+133
src/jsont_pointer.ml
···
706
706
remove p json
707
707
in
708
708
Jsont.map Jsont.json ~dec ~enc:(fun j -> j)
709
+
710
+
(* JMAP Extended Pointers - RFC 8620 Section 3.7 *)
711
+
module Jmap = struct
712
+
(* Extended segment type: regular tokens or wildcard *)
713
+
type segment =
714
+
| Token of string (* Unescaped reference token *)
715
+
| Wildcard (* The * token for array mapping *)
716
+
717
+
type t = segment list
718
+
719
+
let parse_segments s =
720
+
if s = "" then []
721
+
else if s.[0] <> '/' then
722
+
Jsont.Error.msgf Jsont.Meta.none
723
+
"Invalid JMAP Pointer: must be empty or start with '/': %s" s
724
+
else
725
+
let rest = String.sub s 1 (String.length s - 1) in
726
+
let tokens = String.split_on_char '/' rest in
727
+
List.map (fun tok ->
728
+
if tok = "*" then Wildcard
729
+
else if tok = "-" then
730
+
Jsont.Error.msgf Jsont.Meta.none
731
+
"Invalid JMAP Pointer: '-' not supported in result reference paths"
732
+
else Token (Token.unescape tok)
733
+
) tokens
734
+
735
+
let of_string s = parse_segments s
736
+
737
+
let of_string_result s =
738
+
try Ok (of_string s)
739
+
with Jsont.Error e -> Error (Jsont.Error.to_string e)
740
+
741
+
let segment_to_string = function
742
+
| Token s -> Token.escape s
743
+
| Wildcard -> "*"
744
+
745
+
let to_string p =
746
+
if p = [] then ""
747
+
else
748
+
let b = Buffer.create 64 in
749
+
List.iter (fun seg ->
750
+
Buffer.add_char b '/';
751
+
Buffer.add_string b (segment_to_string seg)
752
+
) p;
753
+
Buffer.contents b
754
+
755
+
let pp ppf p = Format.pp_print_string ppf (to_string p)
756
+
757
+
(* Evaluation with wildcard support *)
758
+
let rec eval_segments segments json =
759
+
match segments with
760
+
| [] -> json
761
+
| Wildcard :: rest ->
762
+
(* Wildcard: map through array, flatten results *)
763
+
(match json with
764
+
| Jsont.Array (elements, meta) ->
765
+
let results = List.map (eval_segments rest) elements in
766
+
(* Flatten: if a result is an array, inline its contents *)
767
+
let flattened = List.concat_map (function
768
+
| Jsont.Array (elems, _) -> elems
769
+
| other -> [other]
770
+
) results in
771
+
Jsont.Array (flattened, meta)
772
+
| _ ->
773
+
Jsont.Error.msgf (Jsont.Json.meta json)
774
+
"JMAP Pointer: '*' can only be used on arrays, got %s"
775
+
(json_sort_string json))
776
+
| Token token :: rest ->
777
+
(* Standard token: navigate into object or array *)
778
+
(match json with
779
+
| Jsont.Object (members, _) ->
780
+
(match get_member token members with
781
+
| Some (_, value) -> eval_segments rest value
782
+
| None ->
783
+
Jsont.Error.msgf (Jsont.Json.meta json)
784
+
"JMAP Pointer: member '%s' not found" token)
785
+
| Jsont.Array (elements, _) ->
786
+
(match Token.is_valid_array_index token with
787
+
| Some n ->
788
+
(match get_nth n elements with
789
+
| Some value -> eval_segments rest value
790
+
| None ->
791
+
Jsont.Error.msgf (Jsont.Json.meta json)
792
+
"JMAP Pointer: index %d out of bounds (array has %d elements)"
793
+
n (List.length elements))
794
+
| None ->
795
+
Jsont.Error.msgf (Jsont.Json.meta json)
796
+
"JMAP Pointer: invalid array index '%s'" token)
797
+
| _ ->
798
+
Jsont.Error.msgf (Jsont.Json.meta json)
799
+
"JMAP Pointer: cannot index into %s with '%s'"
800
+
(json_sort_string json) token)
801
+
802
+
let eval p json = eval_segments p json
803
+
804
+
let eval_result p json =
805
+
try Ok (eval p json)
806
+
with Jsont.Error e -> Error e
807
+
808
+
let find p json =
809
+
try Some (eval p json)
810
+
with Jsont.Error _ -> None
811
+
812
+
let jsont : t Jsont.t =
813
+
let dec _meta s = of_string s in
814
+
let enc p = to_string p in
815
+
Jsont.Base.string (Jsont.Base.map
816
+
~kind:"JMAP Pointer"
817
+
~doc:"RFC 8620 JMAP extended JSON Pointer"
818
+
~dec ~enc ())
819
+
820
+
(* Query combinators *)
821
+
822
+
let path ?absent p codec =
823
+
let dec json =
824
+
match find p json with
825
+
| Some extracted ->
826
+
(match Jsont.Json.decode' codec extracted with
827
+
| Ok v -> v
828
+
| Error e -> raise (Jsont.Error e))
829
+
| None ->
830
+
match absent with
831
+
| Some v -> v
832
+
| None ->
833
+
Jsont.Error.msgf Jsont.Meta.none
834
+
"JMAP Pointer %s: path not found" (to_string p)
835
+
in
836
+
Jsont.map Jsont.json ~dec ~enc:(fun _ ->
837
+
Jsont.Error.msgf Jsont.Meta.none "Jmap.path: encode not supported")
838
+
839
+
let path_list p elem_codec =
840
+
path p (Jsont.list elem_codec)
841
+
end
+101
src/jsont_pointer.mli
+101
src/jsont_pointer.mli
···
423
423
424
424
If [allow_absent] is [true] (default [false]), does nothing if
425
425
the pointer doesn't resolve instead of raising. *)
426
+
427
+
(** {1:jmap JMAP Extended Pointers}
428
+
429
+
{{:https://www.rfc-editor.org/rfc/rfc8620#section-3.7}RFC 8620 Section 3.7}
430
+
extends JSON Pointer with a wildcard token [*] for mapping through arrays.
431
+
This is used in JMAP result references.
432
+
433
+
The wildcard semantics are:
434
+
{ul
435
+
{- When the current value is an array and the token is [*], apply the rest
436
+
of the pointer to each element, collecting results into a new array.}
437
+
{- If a mapped result is itself an array, its contents are flattened into
438
+
the output (i.e., array of arrays becomes a single array).}}
439
+
440
+
Example: Given [{"list": \[{"id": "a"}, {"id": "b"}\]}], the extended
441
+
pointer [/list/*/id] evaluates to [["a", "b"]].
442
+
443
+
{b Note}: These extended pointers are {e not} valid RFC 6901 JSON Pointers.
444
+
They should only be used for JMAP result reference resolution. *)
445
+
446
+
module Jmap : sig
447
+
(** JMAP extended JSON Pointer with wildcard support. *)
448
+
449
+
type t
450
+
(** The type for JMAP extended pointers. Unlike standard pointers, these
451
+
may contain [*] tokens for array mapping. *)
452
+
453
+
val of_string : string -> t
454
+
(** [of_string s] parses a JMAP extended pointer.
455
+
456
+
The syntax is the same as RFC 6901 JSON Pointer, except [*] is allowed
457
+
as a reference token for array mapping.
458
+
459
+
@raise Jsont.Error if [s] has invalid syntax. *)
460
+
461
+
val of_string_result : string -> (t, string) result
462
+
(** [of_string_result s] is like {!of_string} but returns a result. *)
463
+
464
+
val to_string : t -> string
465
+
(** [to_string p] serializes [p] to string form. *)
466
+
467
+
val pp : Format.formatter -> t -> unit
468
+
(** [pp] formats a pointer using {!to_string}. *)
469
+
470
+
val eval : t -> Jsont.json -> Jsont.json
471
+
(** [eval p json] evaluates the extended pointer [p] against [json].
472
+
473
+
For [*] tokens on arrays, maps through all elements and collects results.
474
+
Results that are arrays are flattened into the output.
475
+
476
+
@raise Jsont.Error if:
477
+
- A standard token doesn't resolve (member not found, index out of bounds)
478
+
- [*] is used on a non-array value
479
+
- [-] appears in the pointer (not supported in JMAP extended pointers) *)
480
+
481
+
val eval_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
482
+
(** [eval_result p json] is like {!eval} but returns a result. *)
483
+
484
+
val find : t -> Jsont.json -> Jsont.json option
485
+
(** [find p json] is like {!eval} but returns [None] on errors. *)
486
+
487
+
val jsont : t Jsont.t
488
+
(** [jsont] is a {!Jsont.t} codec for JMAP extended pointers. *)
489
+
490
+
(** {2:combinators Query combinators}
491
+
492
+
These combinators integrate JMAP extended pointers with jsont codecs,
493
+
enabling typed extraction from JSON using pointer paths. *)
494
+
495
+
val path : ?absent:'a -> t -> 'a Jsont.t -> 'a Jsont.t
496
+
(** [path p codec] extracts the value at pointer [p] and decodes it with [codec].
497
+
498
+
If [absent] is provided and the pointer doesn't resolve, returns [absent].
499
+
Otherwise raises on pointer resolution failure.
500
+
501
+
Example: Extract all thread IDs from an Email/get response:
502
+
{[
503
+
let thread_ids =
504
+
Jmap.path
505
+
(Jmap.of_string "/list/*/threadId")
506
+
(Jsont.list Jsont.string)
507
+
]}
508
+
509
+
@raise Jsont.Error if the pointer fails to resolve (and no [absent])
510
+
or if decoding with [codec] fails. *)
511
+
512
+
val path_list : t -> 'a Jsont.t -> 'a list Jsont.t
513
+
(** [path_list p codec] extracts the array at pointer [p] and decodes each
514
+
element with [codec].
515
+
516
+
This is a convenience for the common JMAP pattern where wildcards produce
517
+
arrays that need element-wise decoding:
518
+
{[
519
+
(* These are equivalent: *)
520
+
Jmap.path_list (Jmap.of_string "/list/*/id") Jsont.string
521
+
Jmap.path (Jmap.of_string "/list/*/id") (Jsont.list Jsont.string)
522
+
]}
523
+
524
+
@raise Jsont.Error if pointer resolution fails, the result is not an array,
525
+
or any element fails to decode. *)
526
+
end
+4
src/top/dune
+4
src/top/dune
+23
src/top/jsont_pointer_top.ml
+23
src/top/jsont_pointer_top.ml
···
1
+
(* Toplevel printers for Jsont_pointer.t, Jsont.json, and Jsont.Error.t
2
+
3
+
Usage in toplevel:
4
+
#require "jsont-pointer.top";;
5
+
#install_printer Jsont_pointer_top.nav_printer;;
6
+
#install_printer Jsont_pointer_top.append_printer;;
7
+
#install_printer Jsont_pointer_top.json_printer;;
8
+
#install_printer Jsont_pointer_top.error_printer;;
9
+
*)
10
+
11
+
let nav_printer ppf (p : Jsont_pointer.nav Jsont_pointer.t) =
12
+
Jsont_pointer.pp_verbose ppf p
13
+
14
+
let append_printer ppf (p : Jsont_pointer.append Jsont_pointer.t) =
15
+
Jsont_pointer.pp_verbose ppf p
16
+
17
+
let json_printer ppf (json : Jsont.json) =
18
+
match Jsont_bytesrw.encode_string Jsont.json json with
19
+
| Ok s -> Format.pp_print_string ppf s
20
+
| Error e -> Format.fprintf ppf "<json encoding error: %s>" e
21
+
22
+
let error_printer ppf (e : Jsont.Error.t) =
23
+
Format.pp_print_string ppf (Jsont.Error.to_string e)
+44
src/top/jsont_pointer_top.mli
+44
src/top/jsont_pointer_top.mli
···
1
+
(** Toplevel printers for {!Jsont_pointer}, {!Jsont.json}, and {!Jsont.Error.t}.
2
+
3
+
To use in the OCaml toplevel or utop:
4
+
{[
5
+
#require "jsont-pointer.top";;
6
+
#install_printer Jsont_pointer_top.nav_printer;;
7
+
#install_printer Jsont_pointer_top.append_printer;;
8
+
#install_printer Jsont_pointer_top.json_printer;;
9
+
#install_printer Jsont_pointer_top.error_printer;;
10
+
]}
11
+
12
+
After installation, JSON Pointers will display their structure:
13
+
{[
14
+
# Jsont_pointer.of_string_nav "/foo/0";;
15
+
- : Jsont_pointer.nav Jsont_pointer.t = [Mem "foo"; Nth 0]
16
+
]}
17
+
18
+
JSON values will display as formatted JSON strings:
19
+
{[
20
+
# Jsont_bytesrw.decode_string Jsont.json {|{"foo": [1, 2]}|};;
21
+
- : Jsont.json = {"foo": [1, 2]}
22
+
]}
23
+
24
+
And errors will display as readable messages:
25
+
{[
26
+
# Jsont_pointer.of_string "invalid";;
27
+
Exception: Jsont.Error: Invalid JSON Pointer: must be empty or start with '/'
28
+
]} *)
29
+
30
+
val nav_printer : Format.formatter -> Jsont_pointer.nav Jsont_pointer.t -> unit
31
+
(** [nav_printer] formats a navigation JSON Pointer showing its index structure.
32
+
Suitable for use with [#install_printer]. *)
33
+
34
+
val append_printer : Format.formatter -> Jsont_pointer.append Jsont_pointer.t -> unit
35
+
(** [append_printer] formats an append JSON Pointer showing its index structure.
36
+
Suitable for use with [#install_printer]. *)
37
+
38
+
val json_printer : Format.formatter -> Jsont.json -> unit
39
+
(** [json_printer] formats a {!Jsont.json} value as a human-readable
40
+
JSON string. Suitable for use with [#install_printer]. *)
41
+
42
+
val error_printer : Format.formatter -> Jsont.Error.t -> unit
43
+
(** [error_printer] formats a {!Jsont.Error.t} as a human-readable
44
+
error message. Suitable for use with [#install_printer]. *)
+25
test/data/jmap_emails.json
+25
test/data/jmap_emails.json
···
1
+
{
2
+
"accountId": "A1",
3
+
"state": "123456",
4
+
"list": [
5
+
{
6
+
"id": "msg1023",
7
+
"threadId": "trd194",
8
+
"from": [{"email": "alice@example.com"}],
9
+
"subject": "Hello"
10
+
},
11
+
{
12
+
"id": "msg223",
13
+
"threadId": "trd114",
14
+
"from": [{"email": "bob@example.com"}],
15
+
"subject": "Re: Project"
16
+
},
17
+
{
18
+
"id": "msg110",
19
+
"threadId": "trd99",
20
+
"from": [{"email": "carol@example.com"}],
21
+
"subject": "Meeting"
22
+
}
23
+
],
24
+
"notFound": []
25
+
}
+19
test/data/jmap_threads.json
+19
test/data/jmap_threads.json
···
1
+
{
2
+
"accountId": "A1",
3
+
"state": "123456",
4
+
"list": [
5
+
{
6
+
"id": "trd194",
7
+
"emailIds": ["msg1020", "msg1021", "msg1023"]
8
+
},
9
+
{
10
+
"id": "trd114",
11
+
"emailIds": ["msg201", "msg223"]
12
+
},
13
+
{
14
+
"id": "trd99",
15
+
"emailIds": ["msg42"]
16
+
}
17
+
],
18
+
"notFound": []
19
+
}
+3
-1
test/dune
+3
-1
test/dune
+175
test/jmap.t
+175
test/jmap.t
···
1
+
JMAP Extended JSON Pointer Tests (RFC 8620 Section 3.7)
2
+
3
+
This tests the wildcard (*) extension to JSON Pointer for JMAP result references.
4
+
5
+
Parsing JMAP extended pointers:
6
+
7
+
Basic pointers (no wildcards, same as RFC 6901):
8
+
$ ./test_pointer.exe jmap-parse ""
9
+
OK: (root)
10
+
$ ./test_pointer.exe jmap-parse "/foo"
11
+
OK: /foo
12
+
$ ./test_pointer.exe jmap-parse "/foo/0"
13
+
OK: /foo/0
14
+
$ ./test_pointer.exe jmap-parse "/a~1b"
15
+
OK: /a~1b
16
+
17
+
Wildcard pointers:
18
+
$ ./test_pointer.exe jmap-parse "/*"
19
+
OK: /*
20
+
$ ./test_pointer.exe jmap-parse "/list/*"
21
+
OK: /list/*
22
+
$ ./test_pointer.exe jmap-parse "/list/*/id"
23
+
OK: /list/*/id
24
+
$ ./test_pointer.exe jmap-parse "/list/*/emailIds"
25
+
OK: /list/*/emailIds
26
+
$ ./test_pointer.exe jmap-parse "/a/*/b/*/c"
27
+
OK: /a/*/b/*/c
28
+
29
+
Error: "-" not allowed in JMAP pointers:
30
+
$ ./test_pointer.exe jmap-parse "/-"
31
+
ERROR: Invalid JMAP Pointer: '-' not supported in result reference paths
32
+
$ ./test_pointer.exe jmap-parse "/foo/-"
33
+
ERROR: Invalid JMAP Pointer: '-' not supported in result reference paths
34
+
35
+
Error: Invalid syntax:
36
+
$ ./test_pointer.exe jmap-parse "foo"
37
+
ERROR: Invalid JMAP Pointer: must be empty or start with '/': foo
38
+
$ ./test_pointer.exe jmap-parse "/~"
39
+
ERROR: Invalid JSON Pointer: incomplete escape sequence at end
40
+
41
+
Evaluation without wildcards:
42
+
43
+
Root pointer:
44
+
$ ./test_pointer.exe jmap-eval '{"foo":"bar"}' ""
45
+
OK: {"foo":"bar"}
46
+
47
+
Simple member access:
48
+
$ ./test_pointer.exe jmap-eval '{"foo":"bar"}' "/foo"
49
+
OK: "bar"
50
+
51
+
Array index:
52
+
$ ./test_pointer.exe jmap-eval '{"arr":[1,2,3]}' "/arr/1"
53
+
OK: 2
54
+
55
+
Nested access:
56
+
$ ./test_pointer.exe jmap-eval '{"a":{"b":{"c":"deep"}}}' "/a/b/c"
57
+
OK: "deep"
58
+
59
+
Evaluation with wildcards (RFC 8620 examples):
60
+
61
+
Extract single field from each object in array:
62
+
$ ./test_pointer.exe jmap-eval '{"list":[{"id":"a"},{"id":"b"},{"id":"c"}]}' "/list/*/id"
63
+
OK: ["a","b","c"]
64
+
65
+
Extract threadId from Email/get response (RFC 8620 pattern):
66
+
$ ./test_pointer.exe jmap-eval-file data/jmap_emails.json "/list/*/threadId"
67
+
OK: ["trd194","trd114","trd99"]
68
+
69
+
Extract emailIds from Thread/get response (RFC 8620 pattern) - results flattened:
70
+
$ ./test_pointer.exe jmap-eval-file data/jmap_threads.json "/list/*/emailIds"
71
+
OK: ["msg1020","msg1021","msg1023","msg201","msg223","msg42"]
72
+
73
+
Extract nested field:
74
+
$ ./test_pointer.exe jmap-eval '{"items":[{"data":{"value":1}},{"data":{"value":2}}]}' "/items/*/data/value"
75
+
OK: [1,2]
76
+
77
+
Wildcard on empty array:
78
+
$ ./test_pointer.exe jmap-eval '{"list":[]}' "/list/*/id"
79
+
OK: []
80
+
81
+
Multiple wildcards (nested arrays):
82
+
$ ./test_pointer.exe jmap-eval '{"a":[{"b":[{"c":1},{"c":2}]},{"b":[{"c":3}]}]}' "/a/*/b/*/c"
83
+
OK: [1,2,3]
84
+
85
+
Wildcard returning non-arrays (no flattening needed):
86
+
$ ./test_pointer.exe jmap-eval '{"list":[{"name":"alice"},{"name":"bob"}]}' "/list/*/name"
87
+
OK: ["alice","bob"]
88
+
89
+
Flattening behavior - arrays of arrays become flat:
90
+
$ ./test_pointer.exe jmap-eval '{"items":[{"tags":["a","b"]},{"tags":["c"]},{"tags":["d","e","f"]}]}' "/items/*/tags"
91
+
OK: ["a","b","c","d","e","f"]
92
+
93
+
Error cases:
94
+
95
+
Wildcard on non-array:
96
+
$ ./test_pointer.exe jmap-eval '{"obj":{"a":1}}' "/obj/*"
97
+
ERROR: JMAP Pointer: '*' can only be used on arrays, got object
98
+
File "-":
99
+
$ ./test_pointer.exe jmap-eval '"string"' "/*"
100
+
ERROR: JMAP Pointer: '*' can only be used on arrays, got string
101
+
File "-":
102
+
103
+
Member not found:
104
+
$ ./test_pointer.exe jmap-eval '{"foo":"bar"}' "/baz"
105
+
ERROR: JMAP Pointer: member 'baz' not found
106
+
File "-":
107
+
108
+
Index out of bounds:
109
+
$ ./test_pointer.exe jmap-eval '{"arr":[1,2]}' "/arr/5"
110
+
ERROR: JMAP Pointer: index 5 out of bounds (array has 2 elements)
111
+
File "-":
112
+
113
+
Member not found after wildcard:
114
+
$ ./test_pointer.exe jmap-eval '{"list":[{"a":1},{"b":2}]}' "/list/*/a"
115
+
ERROR: JMAP Pointer: member 'a' not found
116
+
File "-":
117
+
118
+
Real JMAP patterns:
119
+
120
+
Get IDs from query response:
121
+
$ ./test_pointer.exe jmap-eval '{"queryState":"abc","ids":["id1","id2","id3"]}' "/ids"
122
+
OK: ["id1","id2","id3"]
123
+
124
+
Get created IDs from changes response:
125
+
$ ./test_pointer.exe jmap-eval '{"oldState":"a","newState":"b","created":["f1","f4"],"updated":[],"destroyed":[]}' "/created"
126
+
OK: ["f1","f4"]
127
+
128
+
Complex nested extraction:
129
+
$ ./test_pointer.exe jmap-eval '{"results":[{"emails":[{"from":"a@b.com"},{"from":"c@d.com"}]},{"emails":[{"from":"e@f.com"}]}]}' "/results/*/emails/*/from"
130
+
OK: ["a@b.com","c@d.com","e@f.com"]
131
+
132
+
Typed extraction with Jmap.path combinator:
133
+
134
+
Extract string list with wildcard:
135
+
$ ./test_pointer.exe jmap-path-strings '{"list":[{"id":"a"},{"id":"b"},{"id":"c"}]}' "/list/*/id"
136
+
OK: [a, b, c]
137
+
138
+
Extract IDs from JMAP-style response:
139
+
$ ./test_pointer.exe jmap-path-strings '{"ids":["id1","id2","id3"]}' "/ids"
140
+
OK: [id1, id2, id3]
141
+
142
+
Extract threadIds (JMAP Email/get pattern):
143
+
$ ./test_pointer.exe jmap-path-strings '{"list":[{"threadId":"t1"},{"threadId":"t2"}]}' "/list/*/threadId"
144
+
OK: [t1, t2]
145
+
146
+
Extract integers:
147
+
$ ./test_pointer.exe jmap-path-ints '{"items":[{"count":10},{"count":20},{"count":30}]}' "/items/*/count"
148
+
OK: [10, 20, 30]
149
+
150
+
Extract single string value:
151
+
$ ./test_pointer.exe jmap-path-single '{"account":{"id":"acc123"}}' "/account/id"
152
+
OK: acc123
153
+
154
+
Extract with absent default (path exists):
155
+
$ ./test_pointer.exe jmap-path-absent '{"name":"alice"}' "/name" "default"
156
+
OK: alice
157
+
158
+
Extract with absent default (path missing):
159
+
$ ./test_pointer.exe jmap-path-absent '{"other":"value"}' "/name" "default"
160
+
OK: default
161
+
162
+
Nested wildcard extraction:
163
+
$ ./test_pointer.exe jmap-path-strings '{"a":[{"b":[{"c":"x"},{"c":"y"}]},{"b":[{"c":"z"}]}]}' "/a/*/b/*/c"
164
+
OK: [x, y, z]
165
+
166
+
Empty array result:
167
+
$ ./test_pointer.exe jmap-path-strings '{"list":[]}' "/list/*/id"
168
+
OK: []
169
+
170
+
Type mismatch error (expecting strings, got ints):
171
+
$ ./test_pointer.exe jmap-path-strings '{"list":[{"id":1},{"id":2}]}' "/list/*/id"
172
+
ERROR: Expected string but found number
173
+
File "-":
174
+
File "-": at index 0 of
175
+
File "-": array<string>
+121
test/test_pointer.ml
+121
test/test_pointer.ml
···
182
182
with Jsont.Error e ->
183
183
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
184
184
185
+
(* Test: JMAP extended pointer parse *)
186
+
let test_jmap_parse pointer_str =
187
+
try
188
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
189
+
let s = Jsont_pointer.Jmap.to_string p in
190
+
if s = "" then Printf.printf "OK: (root)\n"
191
+
else Printf.printf "OK: %s\n" s
192
+
with Jsont.Error e ->
193
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
194
+
195
+
(* Test: JMAP extended pointer evaluation *)
196
+
let test_jmap_eval json_str pointer_str =
197
+
try
198
+
let json = parse_json json_str in
199
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
200
+
let result = Jsont_pointer.Jmap.eval p json in
201
+
Printf.printf "OK: %s\n" (json_to_string result)
202
+
with
203
+
| Jsont.Error e ->
204
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
205
+
| Failure e ->
206
+
Printf.printf "FAIL: %s\n" e
207
+
208
+
(* Test: JMAP extended pointer evaluation from file *)
209
+
let test_jmap_eval_file json_path pointer_str =
210
+
try
211
+
let json = parse_json (read_file json_path) in
212
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
213
+
let result = Jsont_pointer.Jmap.eval p json in
214
+
Printf.printf "OK: %s\n" (json_to_string result)
215
+
with
216
+
| Jsont.Error e ->
217
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
218
+
| Failure e ->
219
+
Printf.printf "FAIL: %s\n" e
220
+
221
+
(* Test: JMAP path combinator - extract strings *)
222
+
let test_jmap_path_strings json_str pointer_str =
223
+
try
224
+
let json = parse_json json_str in
225
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
226
+
let codec = Jsont_pointer.Jmap.path_list p Jsont.string in
227
+
let result = match Jsont.Json.decode' codec json with
228
+
| Ok v -> v
229
+
| Error e -> raise (Jsont.Error e)
230
+
in
231
+
Printf.printf "OK: [%s]\n" (String.concat ", " result)
232
+
with
233
+
| Jsont.Error e ->
234
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
235
+
| Failure e ->
236
+
Printf.printf "FAIL: %s\n" e
237
+
238
+
(* Test: JMAP path combinator - extract ints *)
239
+
let test_jmap_path_ints json_str pointer_str =
240
+
try
241
+
let json = parse_json json_str in
242
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
243
+
let codec = Jsont_pointer.Jmap.path_list p Jsont.int in
244
+
let result = match Jsont.Json.decode' codec json with
245
+
| Ok v -> v
246
+
| Error e -> raise (Jsont.Error e)
247
+
in
248
+
Printf.printf "OK: [%s]\n" (String.concat ", " (List.map string_of_int result))
249
+
with
250
+
| Jsont.Error e ->
251
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
252
+
| Failure e ->
253
+
Printf.printf "FAIL: %s\n" e
254
+
255
+
(* Test: JMAP path combinator - extract single value *)
256
+
let test_jmap_path_single json_str pointer_str =
257
+
try
258
+
let json = parse_json json_str in
259
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
260
+
let codec = Jsont_pointer.Jmap.path p Jsont.string in
261
+
let result = match Jsont.Json.decode' codec json with
262
+
| Ok v -> v
263
+
| Error e -> raise (Jsont.Error e)
264
+
in
265
+
Printf.printf "OK: %s\n" result
266
+
with
267
+
| Jsont.Error e ->
268
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
269
+
| Failure e ->
270
+
Printf.printf "FAIL: %s\n" e
271
+
272
+
(* Test: JMAP path combinator with absent *)
273
+
let test_jmap_path_absent json_str pointer_str default =
274
+
try
275
+
let json = parse_json json_str in
276
+
let p = Jsont_pointer.Jmap.of_string pointer_str in
277
+
let codec = Jsont_pointer.Jmap.path ~absent:default p Jsont.string in
278
+
let result = match Jsont.Json.decode' codec json with
279
+
| Ok v -> v
280
+
| Error e -> raise (Jsont.Error e)
281
+
in
282
+
Printf.printf "OK: %s\n" result
283
+
with
284
+
| Jsont.Error e ->
285
+
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
286
+
| Failure e ->
287
+
Printf.printf "FAIL: %s\n" e
288
+
185
289
let () =
186
290
match Array.to_list Sys.argv with
187
291
| _ :: "parse" :: pointer :: _ ->
···
210
314
test_test json pointer expected
211
315
| _ :: "has" :: json :: pointer :: _ ->
212
316
test_has json pointer
317
+
| _ :: "jmap-parse" :: pointer :: _ ->
318
+
test_jmap_parse pointer
319
+
| _ :: "jmap-eval" :: json :: pointer :: _ ->
320
+
test_jmap_eval json pointer
321
+
| _ :: "jmap-eval-file" :: json_path :: pointer :: _ ->
322
+
test_jmap_eval_file json_path pointer
323
+
| _ :: "jmap-path-strings" :: json :: pointer :: _ ->
324
+
test_jmap_path_strings json pointer
325
+
| _ :: "jmap-path-ints" :: json :: pointer :: _ ->
326
+
test_jmap_path_ints json pointer
327
+
| _ :: "jmap-path-single" :: json :: pointer :: _ ->
328
+
test_jmap_path_single json pointer
329
+
| _ :: "jmap-path-absent" :: json :: pointer :: default :: _ ->
330
+
test_jmap_path_absent json pointer default
213
331
| _ ->
214
332
Printf.printf "Usage:\n";
215
333
Printf.printf " test_pointer parse <pointer>\n";
···
225
343
Printf.printf " test_pointer copy <json> <from> <path>\n";
226
344
Printf.printf " test_pointer test <json> <pointer> <expected>\n";
227
345
Printf.printf " test_pointer has <json> <pointer>\n";
346
+
Printf.printf " test_pointer jmap-parse <pointer>\n";
347
+
Printf.printf " test_pointer jmap-eval <json> <pointer>\n";
348
+
Printf.printf " test_pointer jmap-eval-file <json-file> <pointer>\n";
228
349
exit 1