RFC6901 JSON Pointer implementation in OCaml using jsont

refactor

+20 -32
bin/jsonpp.ml
··· 17 17 | Ok s -> s 18 18 | Error e -> failwith e 19 19 20 - (* Helper to get indices from either nav or append pointer *) 21 - let indices_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t 22 - | `Append of Jsont_pointer.append Jsont_pointer.t ]) = 23 - match result with 24 - | `Nav p -> Jsont_pointer.indices p 25 - | `Append p -> Jsont_pointer.indices p 20 + (* Helper to get indices from any pointer *) 21 + let indices_of_any (Jsont_pointer.Any p) = Jsont_pointer.indices p 26 22 27 - (* Helper to convert to string from either nav or append pointer *) 28 - let to_string_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t 29 - | `Append of Jsont_pointer.append Jsont_pointer.t ]) = 30 - match result with 31 - | `Nav p -> Jsont_pointer.to_string p 32 - | `Append p -> Jsont_pointer.to_string p 23 + (* Helper to convert to string from any pointer *) 24 + let to_string_of_any (Jsont_pointer.Any p) = Jsont_pointer.to_string p 25 + 26 + (* Helper to check if pointer is append *) 27 + let is_append_any (Jsont_pointer.Any p : Jsont_pointer.any) = 28 + not (Jsont_pointer.is_nav (Jsont_pointer.Any p)) 33 29 34 30 (* Test: parse pointer and print indices *) 35 31 let test_parse pointer_str = 36 32 try 37 33 let result = Jsont_pointer.of_string pointer_str in 38 - let indices = indices_of_result result in 34 + let indices = indices_of_any result in 39 35 let index_strs = List.map (fun idx -> 40 36 match idx with 41 37 | Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s 42 38 | Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n 43 39 ) indices in 44 - let suffix = match result with `Nav _ -> "" | `Append _ -> ", /-" in 40 + let suffix = if is_append_any result then ", /-" else "" in 45 41 Printf.printf "OK: [%s%s]\n" (String.concat ", " index_strs) suffix 46 42 with Jsont.Error e -> 47 43 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 50 46 let test_roundtrip pointer_str = 51 47 try 52 48 let result = Jsont_pointer.of_string pointer_str in 53 - let s = to_string_of_result result in 49 + let s = to_string_of_any result in 54 50 if s = pointer_str then 55 51 Printf.printf "OK: %s\n" s 56 52 else ··· 88 84 let test_uri_fragment pointer_str = 89 85 try 90 86 let result = Jsont_pointer.of_string pointer_str in 91 - let frag = match result with 92 - | `Nav p -> Jsont_pointer.to_uri_fragment p 93 - | `Append p -> Jsont_pointer.to_uri_fragment p 94 - in 87 + let (Jsont_pointer.Any p) = result in 88 + let frag = Jsont_pointer.to_uri_fragment p in 95 89 let result2 = Jsont_pointer.of_uri_fragment frag in 96 - let s2 = to_string_of_result result2 in 90 + let s2 = to_string_of_any result2 in 97 91 if s2 = pointer_str then 98 92 Printf.printf "OK: %s -> %s\n" pointer_str frag 99 93 else ··· 106 100 try 107 101 let json = parse_json json_str in 108 102 let value = parse_json value_str in 109 - let result = match Jsont_pointer.of_string pointer_str with 110 - | `Nav p -> Jsont_pointer.add p json ~value 111 - | `Append p -> Jsont_pointer.add p json ~value 112 - in 103 + let p = Jsont_pointer.of_string pointer_str in 104 + let result = Jsont_pointer.add p json ~value in 113 105 Printf.printf "%s\n" (json_to_string result) 114 106 with Jsont.Error e -> 115 107 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 140 132 try 141 133 let json = parse_json json_str in 142 134 let from = Jsont_pointer.of_string_nav from_str in 143 - let result = match Jsont_pointer.of_string path_str with 144 - | `Nav path -> Jsont_pointer.move ~from ~path json 145 - | `Append path -> Jsont_pointer.move ~from ~path json 146 - in 135 + let path = Jsont_pointer.of_string path_str in 136 + let result = Jsont_pointer.move ~from ~path json in 147 137 Printf.printf "%s\n" (json_to_string result) 148 138 with Jsont.Error e -> 149 139 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 153 143 try 154 144 let json = parse_json json_str in 155 145 let from = Jsont_pointer.of_string_nav from_str in 156 - let result = match Jsont_pointer.of_string path_str with 157 - | `Nav path -> Jsont_pointer.copy ~from ~path json 158 - | `Append path -> Jsont_pointer.copy ~from ~path json 159 - in 146 + let path = Jsont_pointer.of_string path_str in 147 + let result = Jsont_pointer.copy ~from ~path json in 160 148 Printf.printf "%s\n" (json_to_string result) 161 149 with Jsont.Error e -> 162 150 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
+152 -102
doc/tutorial.mld
··· 54 54 For example, given this JSON document: 55 55 56 56 {x@ocaml[ 57 - # let users_json = 58 - parse_json "{\"users\":[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]}";; 57 + # let users_json = parse_json {|{ 58 + "users": [ 59 + {"name": "Alice", "age": 30}, 60 + {"name": "Bob", "age": 25} 61 + ] 62 + }|};; 59 63 val users_json : Jsont.json = 60 64 {"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]} 61 65 ]x} ··· 129 133 130 134 Each reference token is represented using {!Jsont.Path.index}: 131 135 132 - {[ 136 + {v 133 137 type index = Jsont.Path.index 134 138 (* = Jsont.Path.Mem of string * Jsont.Meta.t 135 139 | Jsont.Path.Nth of int * Jsont.Meta.t *) 136 - ]} 140 + v} 137 141 138 142 The [Mem] constructor is for object member access, and [Nth] is for array 139 143 index access. The member name is {b unescaped} - you work with the actual ··· 156 160 157 161 {@ocaml[ 158 162 # of_string_result "foo";; 159 - - : ([ `Append of append t | `Nav of nav t ], string) result = 163 + - : (any, string) result = 160 164 Error "Invalid JSON Pointer: must be empty or start with '/': foo" 161 165 # of_string_result "/valid";; 162 - - : ([ `Append of append t | `Nav of nav t ], string) result = 163 - Ok (`Nav [Mem "valid"]) 166 + - : (any, string) result = Ok (Any <abstr>) 164 167 ]} 165 168 166 169 {1 Evaluation: Navigating JSON} ··· 175 178 176 179 Let's use the example JSON document from RFC 6901, Section 5: 177 180 178 - {@ocaml[ 179 - # let rfc_example = parse_json "{\"foo\":[\"bar\",\"baz\"],\"\":0,\"a/b\":1,\"c%d\":2,\"e^f\":3,\"g|h\":4,\"i\\\\j\":5,\"k\\\"l\":6,\" \":7,\"m~n\":8}";; 181 + {x@ocaml[ 182 + # let rfc_example = parse_json {|{ 183 + "foo": ["bar", "baz"], 184 + "": 0, 185 + "a/b": 1, 186 + "c%d": 2, 187 + "e^f": 3, 188 + "g|h": 4, 189 + "i\\j": 5, 190 + "k\"l": 6, 191 + " ": 7, 192 + "m~n": 8 193 + }|};; 180 194 val rfc_example : Jsont.json = 181 195 {"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\j":5,"k\"l":6," ":7,"m~n":8} 182 - ]} 196 + ]x} 183 197 184 198 This document is carefully constructed to exercise various edge cases! 185 199 ··· 304 318 305 319 The library provides both exception-raising and result-returning variants: 306 320 307 - [ 321 + {v 308 322 val get : nav t -> Jsont.json -> Jsont.json 309 323 val get_result : nav t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result 310 324 val find : nav t -> Jsont.json -> Jsont.json option 311 - ] 325 + v} 312 326 313 327 {2 Array Index Rules} 314 328 ··· 355 369 between pointers that can be used for navigation and pointers that target 356 370 the "append position": 357 371 358 - {[ 372 + {v 359 373 type nav (* A pointer to an existing element *) 360 374 type append (* A pointer ending with "-" (append position) *) 361 375 type 'a t (* Pointer with phantom type parameter *) 362 - ]} 376 + type any (* Existential: wraps either nav or append *) 377 + v} 363 378 364 - When you parse a pointer, you get either a [nav t] or an [append t]: 379 + When you parse a pointer with {!of_string}, you get an {!any} pointer 380 + that can be used directly with mutation operations: 365 381 366 382 {@ocaml[ 367 383 # of_string "/foo/0";; 368 - - : [ `Append of Jsont_pointer.append Jsont_pointer.t 369 - | `Nav of Jsont_pointer.nav Jsont_pointer.t ] 370 - = `Nav [Mem "foo"; Nth 0] 384 + - : any = Any <abstr> 371 385 # of_string "/foo/-";; 372 - - : [ `Append of Jsont_pointer.append Jsont_pointer.t 373 - | `Nav of Jsont_pointer.nav Jsont_pointer.t ] 374 - = `Append [Mem "foo"] /- 386 + - : any = Any <abstr> 375 387 ]} 376 388 377 - The [-] creates an [append] pointer. Note that in the internal 378 - representation, the append position is tracked separately (shown as [/-]). 389 + The [-] creates an append pointer. The {!any} type wraps either kind, 390 + making it ergonomic to use with operations like {!set} and {!add}. 379 391 380 - {2 Why Phantom Types?} 392 + {2 Why Two Pointer Types?} 381 393 382 394 The RFC explains that [-] refers to a {e nonexistent} position: 383 395 ··· 387 399 388 400 So you {b cannot use [get] or [find]} with an append pointer - it makes 389 401 no sense to retrieve a value from a position that doesn't exist! The 390 - library enforces this at compile time. 402 + library enforces this: 403 + - Use {!of_string_nav} when you need to call {!get} or {!find} 404 + - Use {!of_string} (returns {!any}) for mutation operations 391 405 392 - However, append pointers {b are} valid for mutation operations like {!add}: 406 + Mutation operations like {!add} accept {!any} directly: 393 407 394 408 {x@ocaml[ 395 - # let arr_obj = parse_json "{\"foo\":[\"a\",\"b\"]}";; 409 + # let arr_obj = parse_json {|{"foo":["a","b"]}|};; 396 410 val arr_obj : Jsont.json = {"foo":["a","b"]} 397 - # (match of_string "/foo/-" with `Append p -> add p arr_obj ~value:(Jsont.Json.string "c") | `Nav _ -> assert false);; 411 + # add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");; 398 412 - : Jsont.json = {"foo":["a","b","c"]} 399 413 ]x} 400 414 401 - For convenience, use {!of_string_nav} when you know a pointer shouldn't 402 - contain [-]: 415 + For retrieval operations, use {!of_string_nav} which ensures the pointer 416 + doesn't contain [-]: 403 417 404 418 {@ocaml[ 405 419 # of_string_nav "/foo/0";; 406 - - : Jsont_pointer.nav Jsont_pointer.t = [Mem "foo"; Nth 0] 420 + - : nav t = [Mem "foo"; Nth 0] 407 421 # of_string_nav "/foo/-";; 408 422 Exception: 409 423 Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer. ··· 415 429 416 430 {@ocaml[ 417 431 # let nav_ptr = of_string_nav "/foo";; 418 - val nav_ptr : Jsont_pointer.nav Jsont_pointer.t = [Mem "foo"] 432 + val nav_ptr : nav t = [Mem "foo"] 419 433 # let app_ptr = at_end nav_ptr;; 420 - val app_ptr : Jsont_pointer.append Jsont_pointer.t = [Mem "foo"] /- 434 + val app_ptr : append t = [Mem "foo"] /- 421 435 # to_string app_ptr;; 422 436 - : string = "/foo/-" 423 437 ]} ··· 428 442 (JSON Patch) uses JSON Pointer for modifications. The [jsont-pointer] 429 443 library provides these operations. 430 444 431 - {2 Which Pointer Type for Which Operation?} 432 - 433 - The phantom type system enforces correct usage: 434 - 435 - {ul 436 - {- {!get}, {!find} - [nav t] only - Can't retrieve from non-existent position} 437 - {- {!remove} - [nav t] only - Can't remove what doesn't exist} 438 - {- {!replace} - [nav t] only - Can't replace what doesn't exist} 439 - {- {!test} - [nav t] only - Can't test non-existent position} 440 - {- {!add} - [_ t] (both) - Can add at existing position OR append} 441 - {- {!set} - [_ t] (both) - Can set existing position OR append} 442 - {- {!move}, {!copy} - [from:nav t], [path:_ t] - Source must exist, dest can be append} 443 - } 444 - 445 445 {2 Add} 446 446 447 - The {!add} operation inserts a value at a location: 447 + The {!add} operation inserts a value at a location. It accepts {!any} 448 + pointers, so you can use {!of_string} directly: 448 449 449 - {@ocaml[ 450 - # let obj = parse_json "{\"foo\":\"bar\"}";; 450 + {x@ocaml[ 451 + # let obj = parse_json {|{"foo":"bar"}|};; 451 452 val obj : Jsont.json = {"foo":"bar"} 452 - # add (of_string_nav "/baz") obj ~value:(Jsont.Json.string "qux") 453 - ;; 453 + # add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");; 454 454 - : Jsont.json = {"foo":"bar","baz":"qux"} 455 - ]} 455 + ]x} 456 456 457 457 For arrays, {!add} inserts BEFORE the specified index: 458 458 459 459 {x@ocaml[ 460 - # let arr_obj = parse_json "{\"foo\":[\"a\",\"b\"]}";; 460 + # let arr_obj = parse_json {|{"foo":["a","b"]}|};; 461 461 val arr_obj : Jsont.json = {"foo":["a","b"]} 462 - # add (of_string_nav "/foo/1") arr_obj ~value:(Jsont.Json.string "X") 463 - ;; 462 + # add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");; 464 463 - : Jsont.json = {"foo":["a","X","b"]} 465 464 ]x} 466 465 467 - This is where the [-] marker and append pointers shine - they append to the end: 466 + This is where the [-] marker shines - it appends to the end: 468 467 469 468 {x@ocaml[ 470 - # (match of_string "/foo/-" with `Append p -> add p arr_obj ~value:(Jsont.Json.string "c") | `Nav _ -> assert false);; 469 + # add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");; 471 470 - : Jsont.json = {"foo":["a","b","c"]} 472 471 ]x} 473 472 474 - Or more conveniently using {!at_end}: 473 + You can also use {!at_end} to create an append pointer programmatically: 475 474 476 475 {x@ocaml[ 477 - # add (at_end (of_string_nav "/foo")) arr_obj ~value:(Jsont.Json.string "c") 478 - ;; 476 + # add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");; 479 477 - : Jsont.json = {"foo":["a","b","c"]} 480 478 ]x} 481 479 480 + {2 Ergonomic Mutation with [any]} 481 + 482 + Since {!add}, {!set}, {!move}, and {!copy} accept {!any} pointers, you can 483 + use {!of_string} directly without any pattern matching. This makes JSON 484 + Patch implementations straightforward: 485 + 486 + {x@ocaml[ 487 + # let items = parse_json {|{"items":["x"]}|};; 488 + val items : Jsont.json = {"items":["x"]} 489 + # add (of_string "/items/0") items ~value:(Jsont.Json.string "y");; 490 + - : Jsont.json = {"items":["y","x"]} 491 + # add (of_string "/items/-") items ~value:(Jsont.Json.string "z");; 492 + - : Jsont.json = {"items":["x","z"]} 493 + ]x} 494 + 495 + The same pointer works whether it targets an existing position or the 496 + append marker - no conditional logic needed. 497 + 482 498 {2 Remove} 483 499 484 500 The {!remove} operation deletes a value. It only accepts [nav t] because 485 501 you can only remove something that exists: 486 502 487 - {@ocaml[ 488 - # let two_fields = parse_json "{\"foo\":\"bar\",\"baz\":\"qux\"}";; 503 + {x@ocaml[ 504 + # let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};; 489 505 val two_fields : Jsont.json = {"foo":"bar","baz":"qux"} 490 506 # remove (of_string_nav "/baz") two_fields ;; 491 507 - : Jsont.json = {"foo":"bar"} 492 - ]} 508 + ]x} 493 509 494 510 For arrays, it removes and shifts: 495 511 496 512 {x@ocaml[ 497 - # let three_elem = parse_json "{\"foo\":[\"a\",\"b\",\"c\"]}";; 513 + # let three_elem = parse_json {|{"foo":["a","b","c"]}|};; 498 514 val three_elem : Jsont.json = {"foo":["a","b","c"]} 499 515 # remove (of_string_nav "/foo/1") three_elem ;; 500 516 - : Jsont.json = {"foo":["a","c"]} ··· 516 532 {2 Move} 517 533 518 534 The {!move} operation relocates a value. The source ([from]) must be a [nav t] 519 - (you can only move something that exists), but the destination ([path]) can 520 - be either: 535 + (you can only move something that exists), but the destination ([path]) 536 + accepts {!any}: 521 537 522 - {@ocaml[ 523 - # let nested = parse_json "{\"foo\":{\"bar\":\"baz\"},\"qux\":{}}";; 538 + {x@ocaml[ 539 + # let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};; 524 540 val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}} 525 - # move ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/qux/thud") nested 526 - ;; 541 + # move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;; 527 542 - : Jsont.json = {"foo":{},"qux":{"thud":"baz"}} 528 - ]} 543 + ]x} 529 544 530 545 {2 Copy} 531 546 532 547 The {!copy} operation duplicates a value (same typing as {!move}): 533 548 534 - {@ocaml[ 535 - # let to_copy = parse_json "{\"foo\":{\"bar\":\"baz\"}}";; 549 + {x@ocaml[ 550 + # let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};; 536 551 val to_copy : Jsont.json = {"foo":{"bar":"baz"}} 537 - # copy ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/foo/qux") to_copy 538 - ;; 552 + # copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;; 539 553 - : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}} 540 - ]} 554 + ]x} 541 555 542 556 {2 Test} 543 557 ··· 574 588 575 589 {@ocaml[ 576 590 # let p = make [mem "a/b"];; 577 - val p : Jsont_pointer.nav Jsont_pointer.t = [Mem "a/b"] 591 + val p : nav t = [Mem "a/b"] 578 592 # to_string p;; 579 593 - : string = "/a~1b" 580 594 # of_string_nav "/a~1b";; 581 - - : Jsont_pointer.nav Jsont_pointer.t = [Mem "a/b"] 595 + - : nav t = [Mem "a/b"] 582 596 ]} 583 597 584 598 {2 Escaping in Action} ··· 657 671 Here's the RFC example showing the URI fragment forms: 658 672 659 673 {ul 660 - {- [""] → [#] → whole document} 661 - {- ["/foo"] → [#/foo] → [["bar", "baz"]]} 662 - {- ["/foo/0"] → [#/foo/0] → ["bar"]} 663 - {- ["/"] → [#/] → [0]} 664 - {- ["/a~1b"] → [#/a~1b] → [1]} 665 - {- ["/c%d"] → [#/c%25d] → [2]} 666 - {- ["/ "] → [#/%20] → [7]} 667 - {- ["/m~0n"] → [#/m~0n] → [8]} 674 + {- [""] -> [#] -> whole document} 675 + {- ["/foo"] -> [#/foo] -> [["bar", "baz"]]} 676 + {- ["/foo/0"] -> [#/foo/0] -> ["bar"]} 677 + {- ["/"] -> [#/] -> [0]} 678 + {- ["/a~1b"] -> [#/a~1b] -> [1]} 679 + {- ["/c%d"] -> [#/c%25d] -> [2]} 680 + {- ["/ "] -> [#/%20] -> [7]} 681 + {- ["/m~0n"] -> [#/m~0n] -> [8]} 668 682 } 669 683 670 684 {1 Building Pointers Programmatically} ··· 673 687 674 688 {@ocaml[ 675 689 # let port_ptr = make [mem "database"; mem "port"];; 676 - val port_ptr : Jsont_pointer.nav Jsont_pointer.t = 677 - [Mem "database"; Mem "port"] 690 + val port_ptr : nav t = [Mem "database"; Mem "port"] 678 691 # to_string port_ptr;; 679 692 - : string = "/database/port" 680 693 ]} ··· 683 696 684 697 {@ocaml[ 685 698 # let first_feature_ptr = make [mem "features"; nth 0];; 686 - val first_feature_ptr : Jsont_pointer.nav Jsont_pointer.t = 687 - [Mem "features"; Nth 0] 699 + val first_feature_ptr : nav t = [Mem "features"; Nth 0] 688 700 # to_string first_feature_ptr;; 689 701 - : string = "/features/0" 690 702 ]} ··· 695 707 696 708 {@ocaml[ 697 709 # let db_ptr = of_string_nav "/database";; 698 - val db_ptr : Jsont_pointer.nav Jsont_pointer.t = [Mem "database"] 710 + val db_ptr : nav t = [Mem "database"] 699 711 # let creds_ptr = db_ptr / mem "credentials";; 700 - val creds_ptr : Jsont_pointer.nav Jsont_pointer.t = 701 - [Mem "database"; Mem "credentials"] 712 + val creds_ptr : nav t = [Mem "database"; Mem "credentials"] 702 713 # let user_ptr = creds_ptr / mem "username";; 703 - val user_ptr : Jsont_pointer.nav Jsont_pointer.t = 704 - [Mem "database"; Mem "credentials"; Mem "username"] 714 + val user_ptr : nav t = [Mem "database"; Mem "credentials"; Mem "username"] 705 715 # to_string user_ptr;; 706 716 - : string = "/database/credentials/username" 707 717 ]} ··· 710 720 711 721 {@ocaml[ 712 722 # let base = of_string_nav "/api/v1";; 713 - val base : Jsont_pointer.nav Jsont_pointer.t = [Mem "api"; Mem "v1"] 723 + val base : nav t = [Mem "api"; Mem "v1"] 714 724 # let endpoint = of_string_nav "/users/0";; 715 - val endpoint : Jsont_pointer.nav Jsont_pointer.t = [Mem "users"; Nth 0] 725 + val endpoint : nav t = [Mem "users"; Nth 0] 716 726 # to_string (concat base endpoint);; 717 727 - : string = "/api/v1/users/0" 718 728 ]} ··· 725 735 directly to an OCaml type. 726 736 727 737 {x@ocaml[ 728 - # let config_json = parse_json "{\"database\":{\"host\":\"localhost\",\"port\":5432,\"credentials\":{\"username\":\"admin\",\"password\":\"secret\"}},\"features\":[\"auth\",\"logging\",\"metrics\"]}";; 738 + # let config_json = parse_json {|{ 739 + "database": { 740 + "host": "localhost", 741 + "port": 5432, 742 + "credentials": {"username": "admin", "password": "secret"} 743 + }, 744 + "features": ["auth", "logging", "metrics"] 745 + }|};; 729 746 val config_json : Jsont.json = 730 747 {"database":{"host":"localhost","port":5432,"credentials":{"username":"admin","password":"secret"}},"features":["auth","logging","metrics"]} 731 748 ]x} ··· 735 752 The {!path} combinator combines pointer navigation with typed decoding: 736 753 737 754 {@ocaml[ 755 + # let nav = of_string_nav "/database/host";; 756 + val nav : nav t = [Mem "database"; Mem "host"] 738 757 # let db_host = 739 758 Jsont.Json.decode 740 - (path (of_string_nav "/database/host") Jsont.string) 759 + (path nav Jsont.string) 741 760 config_json 742 761 |> Result.get_ok;; 743 762 val db_host : string = "localhost" ··· 778 797 You can extract values from deeply nested structures: 779 798 780 799 {x@ocaml[ 781 - # let org_json = parse_json "{\"organization\":{\"owner\":{\"name\":\"Alice\",\"email\":\"alice@example.com\",\"age\":35},\"members\":[{\"name\":\"Bob\",\"email\":\"bob@example.com\",\"age\":28}]}}";; 800 + # let org_json = parse_json {|{ 801 + "organization": { 802 + "owner": {"name": "Alice", "email": "alice@example.com", "age": 35}, 803 + "members": [{"name": "Bob", "email": "bob@example.com", "age": 28}] 804 + } 805 + }|};; 782 806 val org_json : Jsont.json = 783 807 {"organization":{"owner":{"name":"Alice","email":"alice@example.com","age":35},"members":[{"name":"Bob","email":"bob@example.com","age":28}]}} 784 808 # Jsont.Json.decode ··· 818 842 819 843 The typed approach catches mismatches at decode time with clear errors. 820 844 845 + {2 Updates with Polymorphic Pointers} 846 + 847 + The {!set} and {!add} functions accept {!any} pointers, which means you can 848 + use the result of {!of_string} directly without pattern matching: 849 + 850 + {x@ocaml[ 851 + # let tasks = parse_json {|{"tasks":["buy milk"]}|};; 852 + val tasks : Jsont.json = {"tasks":["buy milk"]} 853 + # set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");; 854 + - : Jsont.json = {"tasks":["buy eggs"]} 855 + # set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");; 856 + - : Jsont.json = {"tasks":["buy milk","call mom"]} 857 + ]x} 858 + 859 + This is useful for implementing JSON Patch ([RFC 6902]) where 860 + operations like ["add"] can target either existing positions or the 861 + append marker. If you need to distinguish between pointer types at runtime, 862 + use {!of_string_kind} which returns a polymorphic variant: 863 + 864 + {x@ocaml[ 865 + # of_string_kind "/tasks/0";; 866 + - : [ `Append of append t | `Nav of nav t ] = `Nav [Mem "tasks"; Nth 0] 867 + # of_string_kind "/tasks/-";; 868 + - : [ `Append of append t | `Nav of nav t ] = `Append [Mem "tasks"] /- 869 + ]x} 870 + 821 871 {1 Summary} 822 872 823 873 JSON Pointer (RFC 6901) provides a simple but powerful way to address ··· 829 879 {- {b Evaluation}: Tokens navigate through objects (by key) and arrays (by index)} 830 880 {- {b URI Encoding}: Pointers can be percent-encoded for use in URIs} 831 881 {- {b Mutations}: Combined with JSON Patch (RFC 6902), pointers enable structured updates} 832 - {- {b Type Safety}: Phantom types ([nav t] vs [append t]) prevent misuse of append pointers with retrieval operations} 882 + {- {b Type Safety}: Phantom types ([nav t] vs [append t]) prevent misuse of append pointers with retrieval operations, while the [any] existential type allows ergonomic use with mutation operations} 833 883 } 834 884 835 885 The [jsont-pointer] library implements all of this with type-safe OCaml
+51 -20
src/jsont_pointer.ml
··· 104 104 is_append : bool; (* true if ends with "-" *) 105 105 } 106 106 107 + (* Existential wrapper *) 108 + type any = Any : _ t -> any 109 + 107 110 let root = { segments = []; is_append = false } 108 111 109 112 let is_root p = p.segments = [] && not p.is_append ··· 134 137 135 138 let indices (type a) (p : a t) = List.map Segment.to_index p.segments 136 139 140 + (* Coercion and inspection *) 141 + 142 + let any (type a) (p : a t) : any = Any p 143 + 144 + let is_nav (Any p) = not p.is_append 145 + 146 + let to_nav (Any p) = 147 + if p.is_append then None 148 + else Some { segments = p.segments; is_append = false } 149 + 150 + let to_nav_exn (Any p) = 151 + if p.is_append then 152 + Jsont.Error.msgf Jsont.Meta.none 153 + "JSON Pointer: cannot convert append pointer to nav pointer" 154 + else 155 + { segments = p.segments; is_append = false } 156 + 137 157 (* Parsing *) 138 158 139 159 let parse_segments s = ··· 146 166 let tokens = String.split_on_char '/' rest in 147 167 List.map Segment.of_escaped_string tokens 148 168 149 - let of_string s : [ `Nav of nav t | `Append of append t ] = 169 + let of_string_kind s : [ `Nav of nav t | `Append of append t ] = 150 170 let segments = parse_segments s in 151 171 (* Check if ends with "-" *) 152 172 match List.rev segments with ··· 163 183 "Invalid JSON Pointer: '-' can only appear at the end"; 164 184 `Nav { segments; is_append = false } 165 185 186 + let of_string s : any = 187 + match of_string_kind s with 188 + | `Nav p -> Any p 189 + | `Append p -> Any p 190 + 166 191 let of_string_nav s : nav t = 167 - match of_string s with 192 + match of_string_kind s with 168 193 | `Nav p -> p 169 194 | `Append _ -> 170 195 Jsont.Error.msgf Jsont.Meta.none ··· 203 228 in 204 229 loop 0 205 230 206 - let of_uri_fragment s = of_string (percent_decode s) 231 + let of_uri_fragment s : any = of_string (percent_decode s) 207 232 208 233 let of_uri_fragment_nav s = of_string_nav (percent_decode s) 209 234 210 - let of_uri_fragment_result s = 235 + let of_uri_fragment_result s : (any, string) result = 211 236 try Ok (of_uri_fragment s) 212 237 with Jsont.Error e -> Error (Jsont.Error.to_string e) 213 238 ··· 467 492 | None -> error_index_out_of_bounds json n) 468 493 ~on_other:(fun () -> error_cannot_navigate json) 469 494 470 - let set (type a) (p : a t) json ~value = 495 + let set (Any p) json ~value = 471 496 eval_set_segments p.segments p.is_append value json 472 497 473 498 (* Mutation: add (RFC 6902 semantics) - works with any pointer type *) ··· 528 553 | None -> error_index_out_of_bounds json n) 529 554 ~on_other:(fun () -> error_cannot_navigate json) 530 555 531 - let add (type a) (p : a t) json ~value = 556 + let add (Any p) json ~value = 532 557 eval_add_segments p.segments p.is_append value json 533 558 534 559 (* Mutation: remove - only for nav pointers *) ··· 578 603 579 604 (* Mutation: move *) 580 605 581 - let move ~(from : nav t) ~(path : _ t) json = 606 + let move ~(from : nav t) ~(path : any) json = 607 + let (Any p) = path in 582 608 (* Check for cycle: path cannot be a proper prefix of from *) 583 609 let from_segs = from.segments in 584 - let path_segs = path.segments in 610 + let path_segs = p.segments in 585 611 let rec is_prefix p1 p2 = match p1, p2 with 586 612 | [], _ -> true 587 613 | _, [] -> false 588 614 | h1 :: t1, h2 :: t2 -> String.equal h1 h2 && is_prefix t1 t2 589 615 in 590 616 if is_prefix path_segs from_segs && 591 - not (List.equal String.equal path_segs from_segs && path.is_append = false) then 617 + not (List.equal String.equal path_segs from_segs && p.is_append = false) then 592 618 Jsont.Error.msgf Jsont.Meta.none 593 619 "JSON Pointer: move would create cycle (path is prefix of from)"; 594 620 let value = get from json in ··· 597 623 598 624 (* Mutation: copy *) 599 625 600 - let copy ~(from : nav t) ~(path : _ t) json = 626 + let copy ~(from : nav t) ~(path : any) json = 601 627 let value = get from json in 602 628 add path json ~value 603 629 ··· 608 634 609 635 (* Jsont codec *) 610 636 611 - let jsont : [ `Nav of nav t | `Append of append t ] Jsont.t = 637 + let jsont : any Jsont.t = 612 638 let dec _meta s = of_string s in 639 + let enc (Any p) = to_string p in 640 + Jsont.Base.string (Jsont.Base.map 641 + ~kind:"JSON Pointer" 642 + ~doc:"RFC 6901 JSON Pointer" 643 + ~dec ~enc ()) 644 + 645 + let jsont_kind : [ `Nav of nav t | `Append of append t ] Jsont.t = 646 + let dec _meta s = of_string_kind s in 613 647 let enc = function 614 648 | `Nav p -> to_string p 615 649 | `Append p -> to_string p 616 650 in 617 651 Jsont.Base.string (Jsont.Base.map 618 - ~kind:"JSON Pointer" 619 - ~doc:"RFC 6901 JSON Pointer" 652 + ~kind:"JSON Pointer (kind)" 653 + ~doc:"RFC 6901 JSON Pointer with kind tag" 620 654 ~dec ~enc ()) 621 655 622 656 let jsont_nav : nav t Jsont.t = ··· 627 661 ~doc:"RFC 6901 JSON Pointer (navigation only)" 628 662 ~dec ~enc ()) 629 663 630 - let jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t = 664 + let jsont_uri_fragment : any Jsont.t = 631 665 let dec _meta s = of_uri_fragment s in 632 - let enc = function 633 - | `Nav p -> to_uri_fragment p 634 - | `Append p -> to_uri_fragment p 635 - in 666 + let enc (Any p) = to_uri_fragment p in 636 667 Jsont.Base.string (Jsont.Base.map 637 668 ~kind:"JSON Pointer (URI fragment)" 638 669 ~doc:"RFC 6901 JSON Pointer in URI fragment encoding" ··· 657 688 Jsont.map Jsont.json ~dec ~enc:(fun _ -> 658 689 Jsont.Error.msgf Jsont.Meta.none "path: encode not supported") 659 690 660 - let set_path (type a) ?(allow_absent = false) t (p : a t) v = 691 + let set_path ?(allow_absent = false) t (p : any) v = 661 692 let encoded = match Jsont.Json.encode' t v with 662 693 | Ok json -> json 663 694 | Error e -> raise (Jsont.Error e) ··· 692 723 | Ok j -> j 693 724 | Error e -> raise (Jsont.Error e) 694 725 in 695 - set p json ~value:re_encoded 726 + set (Any p) json ~value:re_encoded 696 727 in 697 728 Jsont.map Jsont.json ~dec ~enc:(fun j -> j) 698 729
+71 -16
src/jsont_pointer.mli
··· 135 135 (** Phantom type for pointers ending with [-] (the "after last element" 136 136 position). These can only be used with {!add} and {!set}. *) 137 137 138 + (** {2 Existential wrapper} 139 + 140 + The {!any} type wraps a pointer of unknown phantom type, allowing 141 + ergonomic use with mutation operations like {!set} and {!add} without 142 + needing to pattern match on the pointer kind. *) 143 + 144 + type any = Any : _ t -> any 145 + (** Existential wrapper for pointers. Use this when you don't need to 146 + distinguish between navigation and append pointers at the type level, 147 + such as when using {!set} or {!add} which accept either kind. *) 148 + 138 149 val root : nav t 139 150 (** [root] is the empty pointer that references the whole document. 140 151 In string form this is [""]. *) ··· 174 185 Note: for append pointers, this returns the indices of the path 175 186 portion; the [-] (append position) is not represented as an index. *) 176 187 188 + (** {2:coercion Coercion and inspection} *) 189 + 190 + val any : _ t -> any 191 + (** [any p] wraps a typed pointer in the existential {!any} type. 192 + Use this when you have a [nav t] or [append t] but need an {!any} 193 + for use with functions like {!set} or {!add}. *) 194 + 195 + val is_nav : any -> bool 196 + (** [is_nav p] is [true] if [p] is a navigation pointer (not an append 197 + pointer ending with [-]). *) 198 + 199 + val to_nav : any -> nav t option 200 + (** [to_nav p] returns [Some nav_p] if [p] is a navigation pointer, 201 + or [None] if it's an append pointer. *) 202 + 203 + val to_nav_exn : any -> nav t 204 + (** [to_nav_exn p] returns the navigation pointer if [p] is one. 205 + @raise Jsont.Error if [p] is an append pointer. *) 206 + 177 207 (** {2:parsing Parsing} *) 178 208 179 - val of_string : string -> [ `Nav of nav t | `Append of append t ] 209 + val of_string : string -> any 180 210 (** [of_string s] parses a JSON Pointer from its string representation. 181 211 182 - Returns [`Nav p] for pointers without [-], or [`Append p] for 183 - pointers ending with [-]. 212 + Returns an {!any} pointer that can be used directly with mutation 213 + operations like {!set} and {!add}. For retrieval operations like 214 + {!get}, use {!of_string_nav} instead. 184 215 185 216 The string must be either empty (representing the root) or start 186 217 with [/]. Each segment between [/] characters is unescaped as a ··· 191 222 - Invalid escape sequence ([~] not followed by [0] or [1]) 192 223 - [-] appears in non-final position *) 193 224 225 + val of_string_kind : string -> [ `Nav of nav t | `Append of append t ] 226 + (** [of_string_kind s] parses a JSON Pointer and returns a tagged variant 227 + indicating whether it's a navigation or append pointer. 228 + 229 + Use this when you need to handle navigation and append pointers 230 + differently, or when you need a typed pointer for operations that 231 + require a specific kind. 232 + 233 + @raise Jsont.Error if [s] has invalid syntax. *) 234 + 194 235 val of_string_nav : string -> nav t 195 236 (** [of_string_nav s] parses a JSON Pointer that must not contain [-]. 237 + 238 + Use this when you need a {!nav} pointer for retrieval operations 239 + like {!get} or {!find}. 196 240 197 241 @raise Jsont.Error if [s] has invalid syntax or contains [-]. *) 198 242 199 - val of_string_result : string -> ([ `Nav of nav t | `Append of append t ], string) result 243 + val of_string_result : string -> (any, string) result 200 244 (** [of_string_result s] is like {!of_string} but returns a result 201 245 instead of raising. *) 202 246 203 - val of_uri_fragment : string -> [ `Nav of nav t | `Append of append t ] 247 + val of_uri_fragment : string -> any 204 248 (** [of_uri_fragment s] parses a JSON Pointer from URI fragment form. 205 249 206 250 This is like {!of_string} but first percent-decodes the string ··· 215 259 216 260 @raise Jsont.Error if invalid or contains [-]. *) 217 261 218 - val of_uri_fragment_result : string -> ([ `Nav of nav t | `Append of append t ], string) result 262 + val of_uri_fragment_result : string -> (any, string) result 219 263 (** [of_uri_fragment_result s] is like {!of_uri_fragment} but returns 220 264 a result instead of raising. *) 221 265 ··· 296 340 All mutation functions return a new JSON value with the modification 297 341 applied; they do not mutate the input. 298 342 299 - Functions that support the [-] token ({!add}, {!set}) accept any 300 - pointer type ([_ t]). Functions that require an existing element 301 - ({!remove}, {!replace}) only accept {!nav} pointers. *) 343 + Functions that support the [-] token ({!set}, {!add}, {!move}, {!copy}) 344 + accept {!any} pointers, making them easy to use with {!of_string}. 345 + Functions that require an existing element ({!remove}, {!replace}) 346 + only accept {!nav} pointers. *) 302 347 303 - val set : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json 348 + val set : any -> Jsont.json -> value:Jsont.json -> Jsont.json 304 349 (** [set p json ~value] replaces the value at pointer [p] with [value]. 305 350 306 351 For {!append} pointers, appends [value] to the end of the array. 307 352 353 + This accepts {!any} pointers directly from {!of_string}: 354 + {[set (of_string "/tasks/-") json ~value:(Jsont.Json.string "new task")]} 355 + 308 356 @raise Jsont.Error if the pointer doesn't resolve to an existing 309 357 location (except for {!append} pointers on arrays). *) 310 358 311 - val add : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json 359 + val add : any -> Jsont.json -> value:Jsont.json -> Jsont.json 312 360 (** [add p json ~value] adds [value] at the location specified by [p]. 313 361 314 362 The behavior depends on the target: ··· 342 390 343 391 @raise Jsont.Error if the pointer doesn't resolve to an existing value. *) 344 392 345 - val move : from:nav t -> path:_ t -> Jsont.json -> Jsont.json 393 + val move : from:nav t -> path:any -> Jsont.json -> Jsont.json 346 394 (** [move ~from ~path json] moves the value from [from] to [path]. 347 395 348 396 This is equivalent to {!remove} at [from] followed by {!add} ··· 352 400 - [from] doesn't resolve to a value 353 401 - [path] is a proper prefix of [from] (would create a cycle) *) 354 402 355 - val copy : from:nav t -> path:_ t -> Jsont.json -> Jsont.json 403 + val copy : from:nav t -> path:any -> Jsont.json -> Jsont.json 356 404 (** [copy ~from ~path json] copies the value from [from] to [path]. 357 405 358 406 This is equivalent to {!get} at [from] followed by {!add} ··· 374 422 These types and functions integrate JSON Pointers with the {!Jsont} 375 423 codec system. *) 376 424 377 - val jsont : [ `Nav of nav t | `Append of append t ] Jsont.t 425 + val jsont : any Jsont.t 378 426 (** [jsont] is a {!Jsont.t} codec for JSON Pointers. 379 427 380 428 On decode, parses a JSON string as a JSON Pointer using {!of_string}. 381 429 On encode, serializes a pointer to a JSON string using {!to_string}. *) 382 430 431 + val jsont_kind : [ `Nav of nav t | `Append of append t ] Jsont.t 432 + (** [jsont_kind] is a {!Jsont.t} codec for JSON Pointers that preserves 433 + the pointer kind in the type. 434 + 435 + On decode, parses using {!of_string_kind}. 436 + On encode, serializes using {!to_string}. *) 437 + 383 438 val jsont_nav : nav t Jsont.t 384 439 (** [jsont_nav] is a {!Jsont.t} codec for navigation JSON Pointers. 385 440 386 441 On decode, parses using {!of_string_nav} (fails on [-]). 387 442 On encode, serializes using {!to_string}. *) 388 443 389 - val jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t 444 + val jsont_uri_fragment : any Jsont.t 390 445 (** [jsont_uri_fragment] is like {!jsont} but uses URI fragment encoding. 391 446 392 447 On decode, parses using {!of_uri_fragment}. ··· 405 460 406 461 This is similar to {!Jsont.path} but uses JSON Pointer syntax. *) 407 462 408 - val set_path : ?allow_absent:bool -> 'a Jsont.t -> _ t -> 'a -> Jsont.json Jsont.t 463 + val set_path : ?allow_absent:bool -> 'a Jsont.t -> any -> 'a -> Jsont.json Jsont.t 409 464 (** [set_path t p v] sets the value at pointer [p] to [v] encoded with [t]. 410 465 411 466 If [allow_absent] is [true] (default [false]), creates missing
+485
test/api.t
··· 1 + JSON Pointer API Test Suite 2 + 3 + This tests all functions exposed in jsont_pointer.mli. 4 + 5 + ================================================================================ 6 + Index Functions (mem, nth, pp_index, equal_index, compare_index) 7 + ================================================================================ 8 + 9 + Creating indices with mem: 10 + $ ./test_pointer.exe mem "foo" 11 + mem(foo) = /foo 12 + $ ./test_pointer.exe mem "" 13 + mem() = / 14 + $ ./test_pointer.exe mem "a/b" 15 + mem(a/b) = /a~1b 16 + $ ./test_pointer.exe mem "m~n" 17 + mem(m~n) = /m~0n 18 + 19 + Creating indices with nth: 20 + $ ./test_pointer.exe nth 0 21 + nth(0) = /0 22 + $ ./test_pointer.exe nth 5 23 + nth(5) = /5 24 + $ ./test_pointer.exe nth 123 25 + nth(123) = /123 26 + 27 + Testing index equality: 28 + $ ./test_pointer.exe equal-index "foo" "foo" 29 + true 30 + $ ./test_pointer.exe equal-index "foo" "bar" 31 + false 32 + $ ./test_pointer.exe equal-index "0" "0" 33 + true 34 + $ ./test_pointer.exe equal-index "0" "1" 35 + false 36 + $ ./test_pointer.exe equal-index "foo" "0" 37 + false 38 + 39 + Testing index comparison: 40 + $ ./test_pointer.exe compare-index "foo" "foo" 41 + EQ 42 + $ ./test_pointer.exe compare-index "aaa" "bbb" 43 + LT 44 + $ ./test_pointer.exe compare-index "bbb" "aaa" 45 + GT 46 + $ ./test_pointer.exe compare-index "0" "0" 47 + EQ 48 + $ ./test_pointer.exe compare-index "0" "5" 49 + LT 50 + $ ./test_pointer.exe compare-index "5" "0" 51 + GT 52 + $ ./test_pointer.exe compare-index "foo" "0" 53 + LT 54 + $ ./test_pointer.exe compare-index "0" "foo" 55 + GT 56 + 57 + ================================================================================ 58 + Pointer Constructors (root, is_root, make, /, append_index, at_end, concat, parent, last) 59 + ================================================================================ 60 + 61 + Root pointer: 62 + $ ./test_pointer.exe root 63 + root = 64 + is_root(root) = true 65 + 66 + Testing is_root: 67 + $ ./test_pointer.exe is-root "" 68 + true 69 + $ ./test_pointer.exe is-root "/foo" 70 + false 71 + $ ./test_pointer.exe is-root "/foo/bar" 72 + false 73 + 74 + Making pointers from indices: 75 + $ ./test_pointer.exe make "foo" 76 + /foo 77 + $ ./test_pointer.exe make "foo,bar" 78 + /foo/bar 79 + $ ./test_pointer.exe make "foo,0,bar" 80 + /foo/0/bar 81 + $ ./test_pointer.exe make "0,1,2" 82 + /0/1/2 83 + 84 + Appending index to pointer: 85 + $ ./test_pointer.exe append-index "" "foo" 86 + /foo 87 + $ ./test_pointer.exe append-index "/foo" "bar" 88 + /foo/bar 89 + $ ./test_pointer.exe append-index "/foo" "0" 90 + /foo/0 91 + $ ./test_pointer.exe append-index "/foo/bar" "baz" 92 + /foo/bar/baz 93 + 94 + Creating append pointers with at_end: 95 + $ ./test_pointer.exe at-end "" 96 + /- 97 + $ ./test_pointer.exe at-end "/foo" 98 + /foo/- 99 + $ ./test_pointer.exe at-end "/foo/bar" 100 + /foo/bar/- 101 + 102 + Concatenating pointers: 103 + $ ./test_pointer.exe concat "" "" 104 + 105 + 106 + $ ./test_pointer.exe concat "/foo" "" 107 + /foo 108 + $ ./test_pointer.exe concat "" "/bar" 109 + /bar 110 + $ ./test_pointer.exe concat "/foo" "/bar" 111 + /foo/bar 112 + $ ./test_pointer.exe concat "/a/b" "/c/d" 113 + /a/b/c/d 114 + 115 + Getting parent pointer: 116 + $ ./test_pointer.exe parent "" 117 + None 118 + $ ./test_pointer.exe parent "/foo" 119 + Some() 120 + $ ./test_pointer.exe parent "/foo/bar" 121 + Some(/foo) 122 + $ ./test_pointer.exe parent "/a/b/c" 123 + Some(/a/b) 124 + 125 + Getting last index: 126 + $ ./test_pointer.exe last "" 127 + None 128 + $ ./test_pointer.exe last "/foo" 129 + Some(/foo) 130 + $ ./test_pointer.exe last "/foo/bar" 131 + Some(/bar) 132 + $ ./test_pointer.exe last "/foo/0" 133 + Some(/0) 134 + 135 + Getting all indices: 136 + $ ./test_pointer.exe indices "" 137 + [] 138 + $ ./test_pointer.exe indices "/foo" 139 + [Mem:foo] 140 + $ ./test_pointer.exe indices "/foo/bar" 141 + [Mem:foo, Mem:bar] 142 + $ ./test_pointer.exe indices "/foo/0/bar" 143 + [Mem:foo, Nth:0, Mem:bar] 144 + $ ./test_pointer.exe indices "/foo/-" 145 + [Mem:foo] 146 + 147 + ================================================================================ 148 + Coercion Functions (any, is_nav, to_nav, to_nav_exn) 149 + ================================================================================ 150 + 151 + Note: is_nav is tested indirectly via the parse tests. Testing to_nav: 152 + 153 + $ ./test_pointer.exe to-nav "" 154 + Some() 155 + $ ./test_pointer.exe to-nav "/foo" 156 + Some(/foo) 157 + $ ./test_pointer.exe to-nav "/foo/bar" 158 + Some(/foo/bar) 159 + $ ./test_pointer.exe to-nav "/-" 160 + None 161 + $ ./test_pointer.exe to-nav "/foo/-" 162 + None 163 + 164 + Testing to_nav_exn: 165 + $ ./test_pointer.exe to-nav-exn "" 166 + OK: 167 + $ ./test_pointer.exe to-nav-exn "/foo" 168 + OK: /foo 169 + $ ./test_pointer.exe to-nav-exn "/-" 170 + ERROR: JSON Pointer: cannot convert append pointer to nav pointer 171 + $ ./test_pointer.exe to-nav-exn "/foo/-" 172 + ERROR: JSON Pointer: cannot convert append pointer to nav pointer 173 + 174 + ================================================================================ 175 + Parsing Functions (of_string_kind, of_string_result, of_uri_fragment_nav, of_uri_fragment_result) 176 + ================================================================================ 177 + 178 + Testing of_string_kind: 179 + $ ./test_pointer.exe of-string-kind "" 180 + Nav() 181 + $ ./test_pointer.exe of-string-kind "/foo" 182 + Nav(/foo) 183 + $ ./test_pointer.exe of-string-kind "/-" 184 + Append(/-) 185 + $ ./test_pointer.exe of-string-kind "/foo/-" 186 + Append(/foo/-) 187 + $ ./test_pointer.exe of-string-kind "/foo/bar" 188 + Nav(/foo/bar) 189 + 190 + Testing of_string_result: 191 + $ ./test_pointer.exe of-string-result "" 192 + Ok() 193 + $ ./test_pointer.exe of-string-result "/foo" 194 + Ok(/foo) 195 + $ ./test_pointer.exe of-string-result "/-" 196 + Ok(/-) 197 + $ ./test_pointer.exe of-string-result "invalid" 198 + Error(Invalid JSON Pointer: must be empty or start with '/': invalid) 199 + $ ./test_pointer.exe of-string-result "/~" 200 + Error(Invalid JSON Pointer: incomplete escape sequence at end) 201 + 202 + Testing of_uri_fragment_nav: 203 + $ ./test_pointer.exe of-uri-fragment-nav "" 204 + OK: 205 + $ ./test_pointer.exe of-uri-fragment-nav "/foo" 206 + OK: /foo 207 + $ ./test_pointer.exe of-uri-fragment-nav "/foo/bar" 208 + OK: /foo/bar 209 + $ ./test_pointer.exe of-uri-fragment-nav "/foo%2Fbar" 210 + OK: /foo/bar 211 + $ ./test_pointer.exe of-uri-fragment-nav "/-" 212 + ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer 213 + 214 + Testing of_uri_fragment_result: 215 + $ ./test_pointer.exe of-uri-fragment-result "" 216 + Ok() 217 + $ ./test_pointer.exe of-uri-fragment-result "/foo" 218 + Ok(/foo) 219 + $ ./test_pointer.exe of-uri-fragment-result "/-" 220 + Ok(/-) 221 + $ ./test_pointer.exe of-uri-fragment-result "invalid" 222 + Error(Invalid JSON Pointer: must be empty or start with '/': invalid) 223 + 224 + ================================================================================ 225 + Pretty Printing (pp, pp_verbose) 226 + ================================================================================ 227 + 228 + Testing pp: 229 + $ ./test_pointer.exe pp "" 230 + 231 + 232 + $ ./test_pointer.exe pp "/foo" 233 + /foo 234 + $ ./test_pointer.exe pp "/foo/bar" 235 + /foo/bar 236 + $ ./test_pointer.exe pp "/-" 237 + /- 238 + $ ./test_pointer.exe pp "/foo/-" 239 + /foo/- 240 + 241 + Testing pp_verbose: 242 + $ ./test_pointer.exe pp-verbose "" 243 + [] 244 + $ ./test_pointer.exe pp-verbose "/foo" 245 + [Mem "foo"] 246 + $ ./test_pointer.exe pp-verbose "/foo/0" 247 + [Mem "foo"; Nth 0] 248 + $ ./test_pointer.exe pp-verbose "/foo/bar/baz" 249 + [Mem "foo"; Mem "bar"; Mem "baz"] 250 + $ ./test_pointer.exe pp-verbose "/-" 251 + [] /- 252 + $ ./test_pointer.exe pp-verbose "/foo/-" 253 + [Mem "foo"] /- 254 + 255 + ================================================================================ 256 + Comparison (equal, compare) 257 + ================================================================================ 258 + 259 + Testing equal: 260 + $ ./test_pointer.exe equal "" "" 261 + true 262 + $ ./test_pointer.exe equal "/foo" "/foo" 263 + true 264 + $ ./test_pointer.exe equal "/foo" "/bar" 265 + false 266 + $ ./test_pointer.exe equal "/foo/0" "/foo/0" 267 + true 268 + $ ./test_pointer.exe equal "/foo/0" "/foo/1" 269 + false 270 + $ ./test_pointer.exe equal "/-" "/-" 271 + true 272 + $ ./test_pointer.exe equal "/foo/-" "/foo/-" 273 + true 274 + $ ./test_pointer.exe equal "/foo" "/foo/-" 275 + false 276 + 277 + Testing compare: 278 + $ ./test_pointer.exe compare "" "" 279 + EQ 280 + $ ./test_pointer.exe compare "/foo" "/foo" 281 + EQ 282 + $ ./test_pointer.exe compare "/aaa" "/bbb" 283 + LT 284 + $ ./test_pointer.exe compare "/bbb" "/aaa" 285 + GT 286 + $ ./test_pointer.exe compare "/foo" "/foo/bar" 287 + LT 288 + $ ./test_pointer.exe compare "/foo/bar" "/foo" 289 + GT 290 + $ ./test_pointer.exe compare "/foo" "/foo/-" 291 + LT 292 + $ ./test_pointer.exe compare "/foo/-" "/foo" 293 + GT 294 + 295 + ================================================================================ 296 + Jsont.Path Conversion (of_path, to_path) 297 + ================================================================================ 298 + 299 + Testing of_path (builds a path programmatically): 300 + $ ./test_pointer.exe of-path 301 + /0/foo/1 302 + 303 + Testing to_path: 304 + $ ./test_pointer.exe to-path "" 305 + [] 306 + $ ./test_pointer.exe to-path "/foo" 307 + [Mem:foo] 308 + $ ./test_pointer.exe to-path "/foo/0" 309 + [Mem:foo, Nth:0] 310 + $ ./test_pointer.exe to-path "/a/b/c" 311 + [Mem:a, Mem:b, Mem:c] 312 + 313 + ================================================================================ 314 + Evaluation (get_result, set) 315 + ================================================================================ 316 + 317 + Testing get_result: 318 + $ ./test_pointer.exe get-result '{"foo":"bar"}' '/foo' 319 + Ok("bar") 320 + $ ./test_pointer.exe get-result '{"foo":{"bar":"baz"}}' '/foo/bar' 321 + Ok("baz") 322 + $ ./test_pointer.exe get-result '["a","b","c"]' '/1' 323 + Ok("b") 324 + $ ./test_pointer.exe get-result '{"foo":"bar"}' '/baz' 325 + Error(JSON Pointer: member 'baz' not found 326 + File "-":) 327 + $ ./test_pointer.exe get-result '["a","b"]' '/5' 328 + Error(JSON Pointer: index 5 out of bounds (array has 2 elements) 329 + File "-":) 330 + 331 + Testing set (with nav pointer): 332 + $ ./test_pointer.exe set '{"foo":"bar"}' '/foo' '"baz"' 333 + {"foo":"baz"} 334 + $ ./test_pointer.exe set '{"foo":{"bar":"old"}}' '/foo/bar' '"new"' 335 + {"foo":{"bar":"new"}} 336 + $ ./test_pointer.exe set '["a","b","c"]' '/1' '"x"' 337 + ["a","x","c"] 338 + 339 + Testing set (with append pointer): 340 + $ ./test_pointer.exe set '["a","b"]' '/-' '"c"' 341 + ["a","b","c"] 342 + $ ./test_pointer.exe set '{"arr":["x"]}' '/arr/-' '"y"' 343 + {"arr":["x","y"]} 344 + 345 + ================================================================================ 346 + Jsont Codecs (jsont, jsont_kind, jsont_nav, jsont_uri_fragment) 347 + ================================================================================ 348 + 349 + Testing jsont codec roundtrip: 350 + $ ./test_pointer.exe jsont-codec "" 351 + "" 352 + $ ./test_pointer.exe jsont-codec "/foo" 353 + "/foo" 354 + $ ./test_pointer.exe jsont-codec "/foo/bar" 355 + "/foo/bar" 356 + $ ./test_pointer.exe jsont-codec "/-" 357 + "/-" 358 + $ ./test_pointer.exe jsont-codec "/foo/-" 359 + "/foo/-" 360 + 361 + Testing jsont_kind codec: 362 + $ ./test_pointer.exe jsont-kind "" 363 + Nav() 364 + $ ./test_pointer.exe jsont-kind "/foo" 365 + Nav(/foo) 366 + $ ./test_pointer.exe jsont-kind "/-" 367 + Append(/-) 368 + $ ./test_pointer.exe jsont-kind "/foo/-" 369 + Append(/foo/-) 370 + 371 + Testing jsont_nav codec: 372 + $ ./test_pointer.exe jsont-nav "" 373 + OK: 374 + $ ./test_pointer.exe jsont-nav "/foo" 375 + OK: /foo 376 + $ ./test_pointer.exe jsont-nav "/-" 377 + ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer 378 + $ ./test_pointer.exe jsont-nav "/foo/-" 379 + ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer 380 + 381 + Testing jsont_uri_fragment codec: 382 + $ ./test_pointer.exe jsont-uri-fragment "" 383 + "" 384 + $ ./test_pointer.exe jsont-uri-fragment "/foo" 385 + "/foo" 386 + $ ./test_pointer.exe jsont-uri-fragment "/a~1b" 387 + "/a~1b" 388 + 389 + ================================================================================ 390 + Query Combinators (path, set_path, update_path, delete_path) 391 + ================================================================================ 392 + 393 + Testing path combinator: 394 + $ ./test_pointer.exe query-path '{"name":"alice"}' '/name' 395 + OK: alice 396 + $ ./test_pointer.exe query-path '{"user":{"name":"bob"}}' '/user/name' 397 + OK: bob 398 + 399 + Testing path combinator with absent: 400 + $ ./test_pointer.exe query-path-absent '{"name":"alice"}' '/name' 'default' 401 + OK: alice 402 + $ ./test_pointer.exe query-path-absent '{"other":"value"}' '/name' 'default' 403 + OK: default 404 + 405 + Testing set_path combinator: 406 + $ ./test_pointer.exe set-path '{"name":"alice"}' '/name' 'bob' 407 + {"name":"bob"} 408 + $ ./test_pointer.exe set-path '{"user":{"name":"old"}}' '/user/name' 'new' 409 + {"user":{"name":"new"}} 410 + 411 + Testing update_path combinator: 412 + $ ./test_pointer.exe update-path '{"name":"alice"}' '/name' 413 + {"name":"alice"} 414 + $ ./test_pointer.exe update-path '{"user":{"name":"bob"}}' '/user/name' 415 + {"user":{"name":"bob"}} 416 + 417 + Testing delete_path combinator: 418 + $ ./test_pointer.exe delete-path '{"foo":"bar","baz":"qux"}' '/foo' 419 + {"baz":"qux"} 420 + $ ./test_pointer.exe delete-path '{"user":{"name":"bob","age":30}}' '/user/name' 421 + {"user":{"age":30}} 422 + 423 + Testing delete_path with allow_absent: 424 + $ ./test_pointer.exe delete-path-absent '{"foo":"bar"}' '/baz' 425 + {"foo":"bar"} 426 + $ ./test_pointer.exe delete-path-absent '{"foo":"bar"}' '/foo' 427 + {} 428 + 429 + ================================================================================ 430 + JMAP Extended Pointers - Additional Tests 431 + ================================================================================ 432 + 433 + Testing Jmap.of_string_result: 434 + $ ./test_pointer.exe jmap-of-string-result "" 435 + Ok() 436 + $ ./test_pointer.exe jmap-of-string-result "/foo" 437 + Ok(/foo) 438 + $ ./test_pointer.exe jmap-of-string-result "/*" 439 + Ok(/*) 440 + $ ./test_pointer.exe jmap-of-string-result "/list/*/id" 441 + Ok(/list/*/id) 442 + $ ./test_pointer.exe jmap-of-string-result "invalid" 443 + Error(Invalid JMAP Pointer: must be empty or start with '/': invalid) 444 + $ ./test_pointer.exe jmap-of-string-result "/-" 445 + Error(Invalid JMAP Pointer: '-' not supported in result reference paths) 446 + 447 + Testing Jmap.pp: 448 + $ ./test_pointer.exe jmap-pp "" 449 + 450 + 451 + $ ./test_pointer.exe jmap-pp "/foo" 452 + /foo 453 + $ ./test_pointer.exe jmap-pp "/*" 454 + /* 455 + $ ./test_pointer.exe jmap-pp "/list/*/id" 456 + /list/*/id 457 + 458 + Testing Jmap.eval_result: 459 + $ ./test_pointer.exe jmap-eval-result '{"foo":"bar"}' '/foo' 460 + Ok("bar") 461 + $ ./test_pointer.exe jmap-eval-result '{"list":[{"id":"a"},{"id":"b"}]}' '/list/*/id' 462 + Ok(["a","b"]) 463 + $ ./test_pointer.exe jmap-eval-result '{"foo":"bar"}' '/baz' 464 + Error(JMAP Pointer: member 'baz' not found 465 + File "-":) 466 + 467 + Testing Jmap.find: 468 + $ ./test_pointer.exe jmap-find '{"foo":"bar"}' '/foo' 469 + Some("bar") 470 + $ ./test_pointer.exe jmap-find '{"list":[{"id":"a"},{"id":"b"}]}' '/list/*/id' 471 + Some(["a","b"]) 472 + $ ./test_pointer.exe jmap-find '{"foo":"bar"}' '/baz' 473 + None 474 + $ ./test_pointer.exe jmap-find '{"obj":{"a":1}}' '/obj/*' 475 + None 476 + 477 + Testing Jmap.jsont codec: 478 + $ ./test_pointer.exe jmap-jsont "" 479 + "" 480 + $ ./test_pointer.exe jmap-jsont "/foo" 481 + "/foo" 482 + $ ./test_pointer.exe jmap-jsont "/*" 483 + "/*" 484 + $ ./test_pointer.exe jmap-jsont "/list/*/id" 485 + "/list/*/id"
+629 -32
test/test_pointer.ml
··· 17 17 | Ok s -> s 18 18 | Error e -> failwith e 19 19 20 - (* Helper to get indices from either nav or append pointer *) 21 - let indices_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t 22 - | `Append of Jsont_pointer.append Jsont_pointer.t ]) = 23 - match result with 24 - | `Nav p -> Jsont_pointer.indices p 25 - | `Append p -> Jsont_pointer.indices p 20 + (* Helper to get indices from any pointer *) 21 + let indices_of_any (Jsont_pointer.Any p) = Jsont_pointer.indices p 22 + 23 + (* Helper to convert to string from any pointer *) 24 + let to_string_of_any (Jsont_pointer.Any p) = Jsont_pointer.to_string p 26 25 27 - (* Helper to convert to string from either nav or append pointer *) 28 - let to_string_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t 29 - | `Append of Jsont_pointer.append Jsont_pointer.t ]) = 30 - match result with 31 - | `Nav p -> Jsont_pointer.to_string p 32 - | `Append p -> Jsont_pointer.to_string p 26 + (* Helper to check if pointer is append *) 27 + let is_append_any p = not (Jsont_pointer.is_nav p) 33 28 34 29 (* Test: parse pointer and print indices *) 35 30 let test_parse pointer_str = 36 31 try 37 32 let result = Jsont_pointer.of_string pointer_str in 38 - let indices = indices_of_result result in 33 + let indices = indices_of_any result in 39 34 let index_strs = List.map (fun idx -> 40 35 match idx with 41 36 | Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s 42 37 | Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n 43 38 ) indices in 44 - let suffix = match result with `Nav _ -> "" | `Append _ -> ", /-" in 39 + let suffix = if is_append_any result then ", /-" else "" in 45 40 Printf.printf "OK: [%s%s]\n" (String.concat ", " index_strs) suffix 46 41 with Jsont.Error e -> 47 42 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 50 45 let test_roundtrip pointer_str = 51 46 try 52 47 let result = Jsont_pointer.of_string pointer_str in 53 - let s = to_string_of_result result in 48 + let s = to_string_of_any result in 54 49 if s = pointer_str then 55 50 Printf.printf "OK: %s\n" s 56 51 else ··· 88 83 let test_uri_fragment pointer_str = 89 84 try 90 85 let result = Jsont_pointer.of_string pointer_str in 91 - let frag = match result with 92 - | `Nav p -> Jsont_pointer.to_uri_fragment p 93 - | `Append p -> Jsont_pointer.to_uri_fragment p 94 - in 86 + let (Jsont_pointer.Any p) = result in 87 + let frag = Jsont_pointer.to_uri_fragment p in 95 88 let result2 = Jsont_pointer.of_uri_fragment frag in 96 - let s2 = to_string_of_result result2 in 89 + let s2 = to_string_of_any result2 in 97 90 if s2 = pointer_str then 98 91 Printf.printf "OK: %s -> %s\n" pointer_str frag 99 92 else ··· 106 99 try 107 100 let json = parse_json json_str in 108 101 let value = parse_json value_str in 109 - let result = match Jsont_pointer.of_string pointer_str with 110 - | `Nav p -> Jsont_pointer.add p json ~value 111 - | `Append p -> Jsont_pointer.add p json ~value 112 - in 102 + let p = Jsont_pointer.of_string pointer_str in 103 + let result = Jsont_pointer.add p json ~value in 113 104 Printf.printf "%s\n" (json_to_string result) 114 105 with Jsont.Error e -> 115 106 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 140 131 try 141 132 let json = parse_json json_str in 142 133 let from = Jsont_pointer.of_string_nav from_str in 143 - let result = match Jsont_pointer.of_string path_str with 144 - | `Nav path -> Jsont_pointer.move ~from ~path json 145 - | `Append path -> Jsont_pointer.move ~from ~path json 146 - in 134 + let path = Jsont_pointer.of_string path_str in 135 + let result = Jsont_pointer.move ~from ~path json in 147 136 Printf.printf "%s\n" (json_to_string result) 148 137 with Jsont.Error e -> 149 138 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 153 142 try 154 143 let json = parse_json json_str in 155 144 let from = Jsont_pointer.of_string_nav from_str in 156 - let result = match Jsont_pointer.of_string path_str with 157 - | `Nav path -> Jsont_pointer.copy ~from ~path json 158 - | `Append path -> Jsont_pointer.copy ~from ~path json 159 - in 145 + let path = Jsont_pointer.of_string path_str in 146 + let result = Jsont_pointer.copy ~from ~path json in 160 147 Printf.printf "%s\n" (json_to_string result) 161 148 with Jsont.Error e -> 162 149 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) ··· 286 273 | Failure e -> 287 274 Printf.printf "FAIL: %s\n" e 288 275 276 + (* Test: index functions - mem, nth, pp_index, equal_index, compare_index *) 277 + let test_mem name = 278 + let idx = Jsont_pointer.mem name in 279 + Format.printf "mem(%s) = %a\n" name Jsont_pointer.pp_index idx 280 + 281 + let test_nth n = 282 + let idx = Jsont_pointer.nth n in 283 + Format.printf "nth(%d) = %a\n" n Jsont_pointer.pp_index idx 284 + 285 + let test_equal_index idx1_str idx2_str = 286 + let parse_idx s = 287 + if String.length s > 0 && s.[0] >= '0' && s.[0] <= '9' then 288 + Jsont_pointer.nth (int_of_string s) 289 + else 290 + Jsont_pointer.mem s 291 + in 292 + let idx1 = parse_idx idx1_str in 293 + let idx2 = parse_idx idx2_str in 294 + Printf.printf "%b\n" (Jsont_pointer.equal_index idx1 idx2) 295 + 296 + let test_compare_index idx1_str idx2_str = 297 + let parse_idx s = 298 + if String.length s > 0 && s.[0] >= '0' && s.[0] <= '9' then 299 + Jsont_pointer.nth (int_of_string s) 300 + else 301 + Jsont_pointer.mem s 302 + in 303 + let idx1 = parse_idx idx1_str in 304 + let idx2 = parse_idx idx2_str in 305 + let cmp = Jsont_pointer.compare_index idx1 idx2 in 306 + if cmp < 0 then Printf.printf "LT\n" 307 + else if cmp > 0 then Printf.printf "GT\n" 308 + else Printf.printf "EQ\n" 309 + 310 + (* Test: pointer constructors - root, is_root, make *) 311 + let test_root () = 312 + let r = Jsont_pointer.root in 313 + Printf.printf "root = %s\n" (Jsont_pointer.to_string r); 314 + Printf.printf "is_root(root) = %b\n" (Jsont_pointer.is_root r) 315 + 316 + let test_is_root pointer_str = 317 + try 318 + let p = Jsont_pointer.of_string pointer_str in 319 + let (Jsont_pointer.Any ptr) = p in 320 + Printf.printf "%b\n" (Jsont_pointer.is_root ptr) 321 + with Jsont.Error e -> 322 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 323 + 324 + let test_make indices_str = 325 + (* Parse comma-separated indices like "foo,0,bar" *) 326 + let parts = String.split_on_char ',' indices_str in 327 + let indices = List.map (fun s -> 328 + let s = String.trim s in 329 + if s = "" then Jsont_pointer.mem "" 330 + else if String.length s > 0 && s.[0] >= '0' && s.[0] <= '9' then 331 + Jsont_pointer.nth (int_of_string s) 332 + else 333 + Jsont_pointer.mem s 334 + ) parts in 335 + let p = Jsont_pointer.make indices in 336 + Printf.printf "%s\n" (Jsont_pointer.to_string p) 337 + 338 + (* Test: append_index and / operator *) 339 + let test_append_index base_str index_str = 340 + try 341 + let base = Jsont_pointer.of_string_nav base_str in 342 + let idx = 343 + if String.length index_str > 0 && index_str.[0] >= '0' && index_str.[0] <= '9' then 344 + Jsont_pointer.nth (int_of_string index_str) 345 + else 346 + Jsont_pointer.mem index_str 347 + in 348 + let result = Jsont_pointer.(base / idx) in 349 + Printf.printf "%s\n" (Jsont_pointer.to_string result) 350 + with Jsont.Error e -> 351 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 352 + 353 + (* Test: at_end *) 354 + let test_at_end pointer_str = 355 + try 356 + let p = Jsont_pointer.of_string_nav pointer_str in 357 + let append_p = Jsont_pointer.at_end p in 358 + Printf.printf "%s\n" (Jsont_pointer.to_string append_p) 359 + with Jsont.Error e -> 360 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 361 + 362 + (* Test: concat *) 363 + let test_concat p1_str p2_str = 364 + try 365 + let p1 = Jsont_pointer.of_string_nav p1_str in 366 + let p2 = Jsont_pointer.of_string_nav p2_str in 367 + let result = Jsont_pointer.concat p1 p2 in 368 + Printf.printf "%s\n" (Jsont_pointer.to_string result) 369 + with Jsont.Error e -> 370 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 371 + 372 + (* Test: parent *) 373 + let test_parent pointer_str = 374 + try 375 + let p = Jsont_pointer.of_string_nav pointer_str in 376 + match Jsont_pointer.parent p with 377 + | Some parent -> Printf.printf "Some(%s)\n" (Jsont_pointer.to_string parent) 378 + | None -> Printf.printf "None\n" 379 + with Jsont.Error e -> 380 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 381 + 382 + (* Test: last *) 383 + let test_last pointer_str = 384 + try 385 + let p = Jsont_pointer.of_string_nav pointer_str in 386 + match Jsont_pointer.last p with 387 + | Some idx -> Format.printf "Some(%a)\n" Jsont_pointer.pp_index idx 388 + | None -> Printf.printf "None\n" 389 + with Jsont.Error e -> 390 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 391 + 392 + (* Test: indices *) 393 + let test_indices pointer_str = 394 + try 395 + let p = Jsont_pointer.of_string pointer_str in 396 + let indices = indices_of_any p in 397 + let strs = List.map (fun idx -> 398 + match idx with 399 + | Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s 400 + | Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n 401 + ) indices in 402 + Printf.printf "[%s]\n" (String.concat ", " strs) 403 + with Jsont.Error e -> 404 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 405 + 406 + (* Test: coercion - to_nav, to_nav_exn *) 407 + let test_to_nav pointer_str = 408 + try 409 + let p = Jsont_pointer.of_string pointer_str in 410 + match Jsont_pointer.to_nav p with 411 + | Some nav -> Printf.printf "Some(%s)\n" (Jsont_pointer.to_string nav) 412 + | None -> Printf.printf "None\n" 413 + with Jsont.Error e -> 414 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 415 + 416 + let test_to_nav_exn pointer_str = 417 + try 418 + let p = Jsont_pointer.of_string pointer_str in 419 + let nav = Jsont_pointer.to_nav_exn p in 420 + Printf.printf "OK: %s\n" (Jsont_pointer.to_string nav) 421 + with Jsont.Error e -> 422 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 423 + 424 + (* Test: of_string_kind *) 425 + let test_of_string_kind pointer_str = 426 + try 427 + match Jsont_pointer.of_string_kind pointer_str with 428 + | `Nav p -> Printf.printf "Nav(%s)\n" (Jsont_pointer.to_string p) 429 + | `Append p -> Printf.printf "Append(%s)\n" (Jsont_pointer.to_string p) 430 + with Jsont.Error e -> 431 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 432 + 433 + (* Test: of_string_result *) 434 + let test_of_string_result pointer_str = 435 + match Jsont_pointer.of_string_result pointer_str with 436 + | Ok p -> Printf.printf "Ok(%s)\n" (to_string_of_any p) 437 + | Error e -> Printf.printf "Error(%s)\n" e 438 + 439 + (* Test: of_uri_fragment_nav *) 440 + let test_of_uri_fragment_nav frag = 441 + try 442 + let p = Jsont_pointer.of_uri_fragment_nav frag in 443 + Printf.printf "OK: %s\n" (Jsont_pointer.to_string p) 444 + with Jsont.Error e -> 445 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 446 + 447 + (* Test: of_uri_fragment_result *) 448 + let test_of_uri_fragment_result frag = 449 + match Jsont_pointer.of_uri_fragment_result frag with 450 + | Ok p -> Printf.printf "Ok(%s)\n" (to_string_of_any p) 451 + | Error e -> Printf.printf "Error(%s)\n" e 452 + 453 + (* Test: pp and pp_verbose *) 454 + let test_pp pointer_str = 455 + try 456 + let p = Jsont_pointer.of_string pointer_str in 457 + let (Jsont_pointer.Any ptr) = p in 458 + Format.printf "%a\n" Jsont_pointer.pp ptr 459 + with Jsont.Error e -> 460 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 461 + 462 + let test_pp_verbose pointer_str = 463 + try 464 + let p = Jsont_pointer.of_string pointer_str in 465 + let (Jsont_pointer.Any ptr) = p in 466 + Format.printf "%a\n" Jsont_pointer.pp_verbose ptr 467 + with Jsont.Error e -> 468 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 469 + 470 + (* Test: equal *) 471 + let test_equal p1_str p2_str = 472 + try 473 + let p1 = Jsont_pointer.of_string p1_str in 474 + let p2 = Jsont_pointer.of_string p2_str in 475 + let (Jsont_pointer.Any ptr1) = p1 in 476 + let (Jsont_pointer.Any ptr2) = p2 in 477 + Printf.printf "%b\n" (Jsont_pointer.equal ptr1 ptr2) 478 + with Jsont.Error e -> 479 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 480 + 481 + (* Test: compare *) 482 + let test_compare p1_str p2_str = 483 + try 484 + let p1 = Jsont_pointer.of_string p1_str in 485 + let p2 = Jsont_pointer.of_string p2_str in 486 + let (Jsont_pointer.Any ptr1) = p1 in 487 + let (Jsont_pointer.Any ptr2) = p2 in 488 + let cmp = Jsont_pointer.compare ptr1 ptr2 in 489 + if cmp < 0 then Printf.printf "LT\n" 490 + else if cmp > 0 then Printf.printf "GT\n" 491 + else Printf.printf "EQ\n" 492 + with Jsont.Error e -> 493 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 494 + 495 + (* Test: of_path and to_path *) 496 + let test_of_path () = 497 + (* Create a Jsont.Path and convert to pointer *) 498 + let path = Jsont.Path.( 499 + root 500 + |> Jsont.Path.nth 0 501 + |> Jsont.Path.mem "foo" 502 + |> Jsont.Path.nth 1 503 + ) in 504 + let p = Jsont_pointer.of_path path in 505 + Printf.printf "%s\n" (Jsont_pointer.to_string p) 506 + 507 + let test_to_path pointer_str = 508 + try 509 + let p = Jsont_pointer.of_string_nav pointer_str in 510 + let path = Jsont_pointer.to_path p in 511 + (* Use rev_indices to get the indices in reverse order *) 512 + let indices = Jsont.Path.rev_indices path in 513 + let parts = List.rev_map (fun idx -> 514 + match idx with 515 + | Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s 516 + | Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n 517 + ) indices in 518 + Printf.printf "[%s]\n" (String.concat ", " parts) 519 + with Jsont.Error e -> 520 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 521 + 522 + (* Test: get_result *) 523 + let test_get_result json_str pointer_str = 524 + try 525 + let json = parse_json json_str in 526 + let p = Jsont_pointer.of_string_nav pointer_str in 527 + match Jsont_pointer.get_result p json with 528 + | Ok result -> Printf.printf "Ok(%s)\n" (json_to_string result) 529 + | Error e -> Printf.printf "Error(%s)\n" (Jsont.Error.to_string e) 530 + with Jsont.Error e -> 531 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 532 + 533 + (* Test: set operation *) 534 + let test_set json_str pointer_str value_str = 535 + try 536 + let json = parse_json json_str in 537 + let value = parse_json value_str in 538 + let p = Jsont_pointer.of_string pointer_str in 539 + let result = Jsont_pointer.set p json ~value in 540 + Printf.printf "%s\n" (json_to_string result) 541 + with Jsont.Error e -> 542 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 543 + 544 + (* Test: jsont codec *) 545 + let test_jsont_codec pointer_str = 546 + try 547 + let json = Jsont.Json.string pointer_str in 548 + let decoded = match Jsont.Json.decode' Jsont_pointer.jsont json with 549 + | Ok p -> p 550 + | Error e -> raise (Jsont.Error e) 551 + in 552 + let encoded = match Jsont.Json.encode' Jsont_pointer.jsont decoded with 553 + | Ok j -> j 554 + | Error e -> raise (Jsont.Error e) 555 + in 556 + let encoded_str = json_to_string encoded in 557 + Printf.printf "%s\n" encoded_str 558 + with Jsont.Error e -> 559 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 560 + 561 + (* Test: jsont_kind codec *) 562 + let test_jsont_kind pointer_str = 563 + try 564 + let json = Jsont.Json.string pointer_str in 565 + let decoded = match Jsont.Json.decode' Jsont_pointer.jsont_kind json with 566 + | Ok p -> p 567 + | Error e -> raise (Jsont.Error e) 568 + in 569 + match decoded with 570 + | `Nav p -> Printf.printf "Nav(%s)\n" (Jsont_pointer.to_string p) 571 + | `Append p -> Printf.printf "Append(%s)\n" (Jsont_pointer.to_string p) 572 + with Jsont.Error e -> 573 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 574 + 575 + (* Test: jsont_nav codec *) 576 + let test_jsont_nav pointer_str = 577 + try 578 + let json = Jsont.Json.string pointer_str in 579 + let decoded = match Jsont.Json.decode' Jsont_pointer.jsont_nav json with 580 + | Ok p -> p 581 + | Error e -> raise (Jsont.Error e) 582 + in 583 + Printf.printf "OK: %s\n" (Jsont_pointer.to_string decoded) 584 + with Jsont.Error e -> 585 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 586 + 587 + (* Test: jsont_uri_fragment codec *) 588 + let test_jsont_uri_fragment pointer_str = 589 + try 590 + (* First parse it normally, then encode as URI fragment *) 591 + let p = Jsont_pointer.of_string pointer_str in 592 + let encoded = match Jsont.Json.encode' Jsont_pointer.jsont_uri_fragment p with 593 + | Ok j -> j 594 + | Error e -> raise (Jsont.Error e) 595 + in 596 + let encoded_str = json_to_string encoded in 597 + Printf.printf "%s\n" encoded_str 598 + with Jsont.Error e -> 599 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 600 + 601 + (* Test: query combinator - path *) 602 + let test_query_path json_str pointer_str = 603 + try 604 + let json = parse_json json_str in 605 + let p = Jsont_pointer.of_string_nav pointer_str in 606 + let codec = Jsont_pointer.path p Jsont.string in 607 + let result = match Jsont.Json.decode' codec json with 608 + | Ok v -> v 609 + | Error e -> raise (Jsont.Error e) 610 + in 611 + Printf.printf "OK: %s\n" result 612 + with Jsont.Error e -> 613 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 614 + 615 + (* Test: query combinator - path with absent *) 616 + let test_query_path_absent json_str pointer_str default = 617 + try 618 + let json = parse_json json_str in 619 + let p = Jsont_pointer.of_string_nav pointer_str in 620 + let codec = Jsont_pointer.path ~absent:default p Jsont.string in 621 + let result = match Jsont.Json.decode' codec json with 622 + | Ok v -> v 623 + | Error e -> raise (Jsont.Error e) 624 + in 625 + Printf.printf "OK: %s\n" result 626 + with Jsont.Error e -> 627 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 628 + 629 + (* Test: query combinator - set_path *) 630 + let test_set_path json_str pointer_str value_str = 631 + try 632 + let json = parse_json json_str in 633 + let p = Jsont_pointer.of_string pointer_str in 634 + let codec = Jsont_pointer.set_path Jsont.string p value_str in 635 + let result = match Jsont.Json.recode' codec json with 636 + | Ok v -> v 637 + | Error e -> raise (Jsont.Error e) 638 + in 639 + Printf.printf "%s\n" (json_to_string result) 640 + with Jsont.Error e -> 641 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 642 + 643 + (* Test: query combinator - update_path *) 644 + let test_update_path json_str pointer_str = 645 + try 646 + let json = parse_json json_str in 647 + let p = Jsont_pointer.of_string_nav pointer_str in 648 + let codec = Jsont_pointer.update_path p Jsont.string in 649 + let result = match Jsont.Json.recode' codec json with 650 + | Ok v -> v 651 + | Error e -> raise (Jsont.Error e) 652 + in 653 + Printf.printf "%s\n" (json_to_string result) 654 + with Jsont.Error e -> 655 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 656 + 657 + (* Test: query combinator - delete_path *) 658 + let test_delete_path json_str pointer_str = 659 + try 660 + let json = parse_json json_str in 661 + let p = Jsont_pointer.of_string_nav pointer_str in 662 + let codec = Jsont_pointer.delete_path p in 663 + let result = match Jsont.Json.recode' codec json with 664 + | Ok v -> v 665 + | Error e -> raise (Jsont.Error e) 666 + in 667 + Printf.printf "%s\n" (json_to_string result) 668 + with Jsont.Error e -> 669 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 670 + 671 + (* Test: query combinator - delete_path with allow_absent *) 672 + let test_delete_path_absent json_str pointer_str = 673 + try 674 + let json = parse_json json_str in 675 + let p = Jsont_pointer.of_string_nav pointer_str in 676 + let codec = Jsont_pointer.delete_path ~allow_absent:true p in 677 + let result = match Jsont.Json.recode' codec json with 678 + | Ok v -> v 679 + | Error e -> raise (Jsont.Error e) 680 + in 681 + Printf.printf "%s\n" (json_to_string result) 682 + with Jsont.Error e -> 683 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 684 + 685 + (* Test: JMAP of_string_result *) 686 + let test_jmap_of_string_result pointer_str = 687 + match Jsont_pointer.Jmap.of_string_result pointer_str with 688 + | Ok p -> Printf.printf "Ok(%s)\n" (Jsont_pointer.Jmap.to_string p) 689 + | Error e -> Printf.printf "Error(%s)\n" e 690 + 691 + (* Test: JMAP pp *) 692 + let test_jmap_pp pointer_str = 693 + try 694 + let p = Jsont_pointer.Jmap.of_string pointer_str in 695 + Format.printf "%a\n" Jsont_pointer.Jmap.pp p 696 + with Jsont.Error e -> 697 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 698 + 699 + (* Test: JMAP eval_result *) 700 + let test_jmap_eval_result json_str pointer_str = 701 + try 702 + let json = parse_json json_str in 703 + let p = Jsont_pointer.Jmap.of_string pointer_str in 704 + match Jsont_pointer.Jmap.eval_result p json with 705 + | Ok result -> Printf.printf "Ok(%s)\n" (json_to_string result) 706 + | Error e -> Printf.printf "Error(%s)\n" (Jsont.Error.to_string e) 707 + with Jsont.Error e -> 708 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 709 + 710 + (* Test: JMAP find *) 711 + let test_jmap_find json_str pointer_str = 712 + try 713 + let json = parse_json json_str in 714 + let p = Jsont_pointer.Jmap.of_string pointer_str in 715 + match Jsont_pointer.Jmap.find p json with 716 + | Some result -> Printf.printf "Some(%s)\n" (json_to_string result) 717 + | None -> Printf.printf "None\n" 718 + with Jsont.Error e -> 719 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 720 + 721 + (* Test: JMAP jsont codec *) 722 + let test_jmap_jsont pointer_str = 723 + try 724 + let json = Jsont.Json.string pointer_str in 725 + let decoded = match Jsont.Json.decode' Jsont_pointer.Jmap.jsont json with 726 + | Ok p -> p 727 + | Error e -> raise (Jsont.Error e) 728 + in 729 + let encoded = match Jsont.Json.encode' Jsont_pointer.Jmap.jsont decoded with 730 + | Ok j -> j 731 + | Error e -> raise (Jsont.Error e) 732 + in 733 + let encoded_str = json_to_string encoded in 734 + Printf.printf "%s\n" encoded_str 735 + with Jsont.Error e -> 736 + Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e) 737 + 289 738 let () = 290 739 match Array.to_list Sys.argv with 291 740 | _ :: "parse" :: pointer :: _ -> ··· 328 777 test_jmap_path_single json pointer 329 778 | _ :: "jmap-path-absent" :: json :: pointer :: default :: _ -> 330 779 test_jmap_path_absent json pointer default 780 + (* Index functions *) 781 + | _ :: "mem" :: name :: _ -> 782 + test_mem name 783 + | _ :: "nth" :: n :: _ -> 784 + test_nth (int_of_string n) 785 + | _ :: "equal-index" :: idx1 :: idx2 :: _ -> 786 + test_equal_index idx1 idx2 787 + | _ :: "compare-index" :: idx1 :: idx2 :: _ -> 788 + test_compare_index idx1 idx2 789 + (* Pointer constructors *) 790 + | _ :: "root" :: _ -> 791 + test_root () 792 + | _ :: "is-root" :: pointer :: _ -> 793 + test_is_root pointer 794 + | _ :: "make" :: indices :: _ -> 795 + test_make indices 796 + | _ :: "append-index" :: base :: index :: _ -> 797 + test_append_index base index 798 + | _ :: "at-end" :: pointer :: _ -> 799 + test_at_end pointer 800 + | _ :: "concat" :: p1 :: p2 :: _ -> 801 + test_concat p1 p2 802 + | _ :: "parent" :: pointer :: _ -> 803 + test_parent pointer 804 + | _ :: "last" :: pointer :: _ -> 805 + test_last pointer 806 + | _ :: "indices" :: pointer :: _ -> 807 + test_indices pointer 808 + (* Coercion *) 809 + | _ :: "to-nav" :: pointer :: _ -> 810 + test_to_nav pointer 811 + | _ :: "to-nav-exn" :: pointer :: _ -> 812 + test_to_nav_exn pointer 813 + (* Parsing variants *) 814 + | _ :: "of-string-kind" :: pointer :: _ -> 815 + test_of_string_kind pointer 816 + | _ :: "of-string-result" :: pointer :: _ -> 817 + test_of_string_result pointer 818 + | _ :: "of-uri-fragment-nav" :: frag :: _ -> 819 + test_of_uri_fragment_nav frag 820 + | _ :: "of-uri-fragment-result" :: frag :: _ -> 821 + test_of_uri_fragment_result frag 822 + (* Pretty printing *) 823 + | _ :: "pp" :: pointer :: _ -> 824 + test_pp pointer 825 + | _ :: "pp-verbose" :: pointer :: _ -> 826 + test_pp_verbose pointer 827 + (* Comparison *) 828 + | _ :: "equal" :: p1 :: p2 :: _ -> 829 + test_equal p1 p2 830 + | _ :: "compare" :: p1 :: p2 :: _ -> 831 + test_compare p1 p2 832 + (* Path conversion *) 833 + | _ :: "of-path" :: _ -> 834 + test_of_path () 835 + | _ :: "to-path" :: pointer :: _ -> 836 + test_to_path pointer 837 + (* Evaluation *) 838 + | _ :: "get-result" :: json :: pointer :: _ -> 839 + test_get_result json pointer 840 + | _ :: "set" :: json :: pointer :: value :: _ -> 841 + test_set json pointer value 842 + (* Jsont codecs *) 843 + | _ :: "jsont-codec" :: pointer :: _ -> 844 + test_jsont_codec pointer 845 + | _ :: "jsont-kind" :: pointer :: _ -> 846 + test_jsont_kind pointer 847 + | _ :: "jsont-nav" :: pointer :: _ -> 848 + test_jsont_nav pointer 849 + | _ :: "jsont-uri-fragment" :: pointer :: _ -> 850 + test_jsont_uri_fragment pointer 851 + (* Query combinators *) 852 + | _ :: "query-path" :: json :: pointer :: _ -> 853 + test_query_path json pointer 854 + | _ :: "query-path-absent" :: json :: pointer :: default :: _ -> 855 + test_query_path_absent json pointer default 856 + | _ :: "set-path" :: json :: pointer :: value :: _ -> 857 + test_set_path json pointer value 858 + | _ :: "update-path" :: json :: pointer :: _ -> 859 + test_update_path json pointer 860 + | _ :: "delete-path" :: json :: pointer :: _ -> 861 + test_delete_path json pointer 862 + | _ :: "delete-path-absent" :: json :: pointer :: _ -> 863 + test_delete_path_absent json pointer 864 + (* JMAP extras *) 865 + | _ :: "jmap-of-string-result" :: pointer :: _ -> 866 + test_jmap_of_string_result pointer 867 + | _ :: "jmap-pp" :: pointer :: _ -> 868 + test_jmap_pp pointer 869 + | _ :: "jmap-eval-result" :: json :: pointer :: _ -> 870 + test_jmap_eval_result json pointer 871 + | _ :: "jmap-find" :: json :: pointer :: _ -> 872 + test_jmap_find json pointer 873 + | _ :: "jmap-jsont" :: pointer :: _ -> 874 + test_jmap_jsont pointer 331 875 | _ -> 332 876 Printf.printf "Usage:\n"; 333 877 Printf.printf " test_pointer parse <pointer>\n"; ··· 346 890 Printf.printf " test_pointer jmap-parse <pointer>\n"; 347 891 Printf.printf " test_pointer jmap-eval <json> <pointer>\n"; 348 892 Printf.printf " test_pointer jmap-eval-file <json-file> <pointer>\n"; 893 + Printf.printf " -- Index functions --\n"; 894 + Printf.printf " test_pointer mem <name>\n"; 895 + Printf.printf " test_pointer nth <n>\n"; 896 + Printf.printf " test_pointer equal-index <idx1> <idx2>\n"; 897 + Printf.printf " test_pointer compare-index <idx1> <idx2>\n"; 898 + Printf.printf " -- Pointer constructors --\n"; 899 + Printf.printf " test_pointer root\n"; 900 + Printf.printf " test_pointer is-root <pointer>\n"; 901 + Printf.printf " test_pointer make <indices>\n"; 902 + Printf.printf " test_pointer append-index <base> <index>\n"; 903 + Printf.printf " test_pointer at-end <pointer>\n"; 904 + Printf.printf " test_pointer concat <p1> <p2>\n"; 905 + Printf.printf " test_pointer parent <pointer>\n"; 906 + Printf.printf " test_pointer last <pointer>\n"; 907 + Printf.printf " test_pointer indices <pointer>\n"; 908 + Printf.printf " -- Coercion --\n"; 909 + Printf.printf " test_pointer to-nav <pointer>\n"; 910 + Printf.printf " test_pointer to-nav-exn <pointer>\n"; 911 + Printf.printf " -- Parsing variants --\n"; 912 + Printf.printf " test_pointer of-string-kind <pointer>\n"; 913 + Printf.printf " test_pointer of-string-result <pointer>\n"; 914 + Printf.printf " test_pointer of-uri-fragment-nav <frag>\n"; 915 + Printf.printf " test_pointer of-uri-fragment-result <frag>\n"; 916 + Printf.printf " -- Pretty printing --\n"; 917 + Printf.printf " test_pointer pp <pointer>\n"; 918 + Printf.printf " test_pointer pp-verbose <pointer>\n"; 919 + Printf.printf " -- Comparison --\n"; 920 + Printf.printf " test_pointer equal <p1> <p2>\n"; 921 + Printf.printf " test_pointer compare <p1> <p2>\n"; 922 + Printf.printf " -- Path conversion --\n"; 923 + Printf.printf " test_pointer of-path\n"; 924 + Printf.printf " test_pointer to-path <pointer>\n"; 925 + Printf.printf " -- Evaluation --\n"; 926 + Printf.printf " test_pointer get-result <json> <pointer>\n"; 927 + Printf.printf " test_pointer set <json> <pointer> <value>\n"; 928 + Printf.printf " -- Jsont codecs --\n"; 929 + Printf.printf " test_pointer jsont-codec <pointer>\n"; 930 + Printf.printf " test_pointer jsont-kind <pointer>\n"; 931 + Printf.printf " test_pointer jsont-nav <pointer>\n"; 932 + Printf.printf " test_pointer jsont-uri-fragment <pointer>\n"; 933 + Printf.printf " -- Query combinators --\n"; 934 + Printf.printf " test_pointer query-path <json> <pointer>\n"; 935 + Printf.printf " test_pointer query-path-absent <json> <pointer> <default>\n"; 936 + Printf.printf " test_pointer set-path <json> <pointer> <value>\n"; 937 + Printf.printf " test_pointer update-path <json> <pointer>\n"; 938 + Printf.printf " test_pointer delete-path <json> <pointer>\n"; 939 + Printf.printf " test_pointer delete-path-absent <json> <pointer>\n"; 940 + Printf.printf " -- JMAP extras --\n"; 941 + Printf.printf " test_pointer jmap-of-string-result <pointer>\n"; 942 + Printf.printf " test_pointer jmap-pp <pointer>\n"; 943 + Printf.printf " test_pointer jmap-eval-result <json> <pointer>\n"; 944 + Printf.printf " test_pointer jmap-find <json> <pointer>\n"; 945 + Printf.printf " test_pointer jmap-jsont <pointer>\n"; 349 946 exit 1