RFC6901 JSON Pointer implementation in OCaml using jsont
1JMAP Extended JSON Pointer Tests (RFC 8620 Section 3.7)
2
3This tests the wildcard (*) extension to JSON Pointer for JMAP result references.
4
5Parsing JMAP extended pointers:
6
7Basic 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
17Wildcard 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
29Error: "-" 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
35Error: 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
41Evaluation without wildcards:
42
43Root pointer:
44 $ ./test_pointer.exe jmap-eval '{"foo":"bar"}' ""
45 OK: {"foo":"bar"}
46
47Simple member access:
48 $ ./test_pointer.exe jmap-eval '{"foo":"bar"}' "/foo"
49 OK: "bar"
50
51Array index:
52 $ ./test_pointer.exe jmap-eval '{"arr":[1,2,3]}' "/arr/1"
53 OK: 2
54
55Nested access:
56 $ ./test_pointer.exe jmap-eval '{"a":{"b":{"c":"deep"}}}' "/a/b/c"
57 OK: "deep"
58
59Evaluation with wildcards (RFC 8620 examples):
60
61Extract 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
65Extract 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
69Extract 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
73Extract nested field:
74 $ ./test_pointer.exe jmap-eval '{"items":[{"data":{"value":1}},{"data":{"value":2}}]}' "/items/*/data/value"
75 OK: [1,2]
76
77Wildcard on empty array:
78 $ ./test_pointer.exe jmap-eval '{"list":[]}' "/list/*/id"
79 OK: []
80
81Multiple 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
85Wildcard returning non-arrays (no flattening needed):
86 $ ./test_pointer.exe jmap-eval '{"list":[{"name":"alice"},{"name":"bob"}]}' "/list/*/name"
87 OK: ["alice","bob"]
88
89Flattening 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
93Error cases:
94
95Wildcard 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
103Member not found:
104 $ ./test_pointer.exe jmap-eval '{"foo":"bar"}' "/baz"
105 ERROR: JMAP Pointer: member 'baz' not found
106 File "-":
107
108Index 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
113Member 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
118Real JMAP patterns:
119
120Get IDs from query response:
121 $ ./test_pointer.exe jmap-eval '{"queryState":"abc","ids":["id1","id2","id3"]}' "/ids"
122 OK: ["id1","id2","id3"]
123
124Get 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
128Complex 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
132Typed extraction with Jmap.path combinator:
133
134Extract 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
138Extract IDs from JMAP-style response:
139 $ ./test_pointer.exe jmap-path-strings '{"ids":["id1","id2","id3"]}' "/ids"
140 OK: [id1, id2, id3]
141
142Extract threadIds (JMAP Email/get pattern):
143 $ ./test_pointer.exe jmap-path-strings '{"list":[{"threadId":"t1"},{"threadId":"t2"}]}' "/list/*/threadId"
144 OK: [t1, t2]
145
146Extract integers:
147 $ ./test_pointer.exe jmap-path-ints '{"items":[{"count":10},{"count":20},{"count":30}]}' "/items/*/count"
148 OK: [10, 20, 30]
149
150Extract single string value:
151 $ ./test_pointer.exe jmap-path-single '{"account":{"id":"acc123"}}' "/account/id"
152 OK: acc123
153
154Extract with absent default (path exists):
155 $ ./test_pointer.exe jmap-path-absent '{"name":"alice"}' "/name" "default"
156 OK: alice
157
158Extract with absent default (path missing):
159 $ ./test_pointer.exe jmap-path-absent '{"other":"value"}' "/name" "default"
160 OK: default
161
162Nested 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
166Empty array result:
167 $ ./test_pointer.exe jmap-path-strings '{"list":[]}' "/list/*/id"
168 OK: []
169
170Type 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>