RFC6901 JSON Pointer implementation in OCaml using jsont

deps

+17 -1
.gitignore
··· 1 - _build
··· 1 + # OCaml build artifacts 2 + _build/ 3 + *.install 4 + *.merlin 5 + 6 + # Third-party sources (fetch locally with opam source) 7 + third_party/ 8 + 9 + # Editor and OS files 10 + .DS_Store 11 + *.swp 12 + *~ 13 + .vscode/ 14 + .idea/ 15 + 16 + # Opam local switch 17 + _opam/
+1
.ocamlformat
···
··· 1 + version=0.28.1
+53
.tangled/workflows/build.yml
···
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: ["main"] 4 + 5 + engine: nixery 6 + 7 + dependencies: 8 + nixpkgs: 9 + - shell 10 + - stdenv 11 + - findutils 12 + - binutils 13 + - libunwind 14 + - ncurses 15 + - opam 16 + - git 17 + - gawk 18 + - gnupatch 19 + - gnum4 20 + - gnumake 21 + - gnutar 22 + - gnused 23 + - gnugrep 24 + - diffutils 25 + - gzip 26 + - bzip2 27 + - gcc 28 + - ocaml 29 + - pkg-config 30 + 31 + steps: 32 + - name: opam 33 + command: | 34 + opam init --disable-sandboxing -a -y 35 + - name: repo 36 + command: | 37 + opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git 38 + - name: switch 39 + command: | 40 + opam install . --confirm-level=unsafe-yes --deps-only 41 + - name: build 42 + command: | 43 + opam exec -- dune build -p json-pointer 44 + - name: switch-test 45 + command: | 46 + opam install . --confirm-level=unsafe-yes --deps-only --with-test 47 + - name: test 48 + command: | 49 + opam exec -- dune runtest --verbose 50 + - name: doc 51 + command: | 52 + opam install -y odoc 53 + opam exec -- dune build @doc
+15
LICENSE.md
···
··· 1 + ISC License 2 + 3 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org> 4 + 5 + Permission to use, copy, modify, and distribute this software for any 6 + purpose with or without fee is hereby granted, provided that the above 7 + copyright notice and this permission notice appear in all copies. 8 + 9 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+1
dune
···
··· 1 + (data_only_dirs third_party)
+13 -7
dune-project
··· 1 - (lang dune 3.17) 2 (name jsont-pointer) 3 - (version 0.1.0) 4 5 (generate_opam_files true) 6 7 - (source (github avsm/jsont-pointer)) 8 (license ISC) 9 (authors "Anil Madhavapeddy") 10 - (maintainers "anil@recoil.org") 11 12 (package 13 (name jsont-pointer) 14 (synopsis "RFC 6901 JSON Pointer implementation for jsont") 15 - (description "This library provides RFC 6901 JSON Pointer parsing, serialization, and evaluation compatible with jsont codecs. It also provides mutation operations suitable for implementing RFC 6902 JSON Patch.") 16 (depends 17 - (ocaml (>= 4.14.0)) 18 - (jsont (>= 0.2.0))))
··· 1 + (lang dune 3.20) 2 + 3 (name jsont-pointer) 4 5 (generate_opam_files true) 6 7 (license ISC) 8 (authors "Anil Madhavapeddy") 9 + (homepage "https://tangled.org/@anil.recoil.org/ocaml-json-pointer") 10 + (maintainers "Anil Madhavapeddy <anil@recoil.org>") 11 + (bug_reports "https://tangled.org/@anil.recoil.org/ocaml-json-pointer/issues") 12 + (maintenance_intent "(latest)") 13 14 (package 15 (name jsont-pointer) 16 (synopsis "RFC 6901 JSON Pointer implementation for jsont") 17 + (description 18 + "This library provides RFC 6901 JSON Pointer parsing, serialization, \ 19 + and evaluation compatible with jsont codecs. It also provides mutation \ 20 + operations suitable for implementing RFC 6902 JSON Patch.") 21 (depends 22 + ocaml 23 + (jsont (>= 0.2.0)) 24 + (odoc :with-doc)))
+6 -7
jsont-pointer.opam
··· 1 # This file is generated by dune, edit dune-project instead 2 opam-version: "2.0" 3 - version: "0.1.0" 4 synopsis: "RFC 6901 JSON Pointer implementation for jsont" 5 description: 6 "This library provides RFC 6901 JSON Pointer parsing, serialization, and evaluation compatible with jsont codecs. It also provides mutation operations suitable for implementing RFC 6902 JSON Patch." 7 - maintainer: ["anil@recoil.org"] 8 authors: ["Anil Madhavapeddy"] 9 license: "ISC" 10 - homepage: "https://github.com/avsm/jsont-pointer" 11 - bug-reports: "https://github.com/avsm/jsont-pointer/issues" 12 depends: [ 13 - "dune" {>= "3.17"} 14 - "ocaml" {>= "4.14.0"} 15 "jsont" {>= "0.2.0"} 16 "odoc" {with-doc} 17 ] ··· 29 "@doc" {with-doc} 30 ] 31 ] 32 - dev-repo: "git+https://github.com/avsm/jsont-pointer.git"
··· 1 # This file is generated by dune, edit dune-project instead 2 opam-version: "2.0" 3 synopsis: "RFC 6901 JSON Pointer implementation for jsont" 4 description: 5 "This library provides RFC 6901 JSON Pointer parsing, serialization, and evaluation compatible with jsont codecs. It also provides mutation operations suitable for implementing RFC 6902 JSON Patch." 6 + maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 authors: ["Anil Madhavapeddy"] 8 license: "ISC" 9 + homepage: "https://tangled.org/@anil.recoil.org/ocaml-json-pointer" 10 + bug-reports: "https://tangled.org/@anil.recoil.org/ocaml-json-pointer/issues" 11 depends: [ 12 + "dune" {>= "3.20"} 13 + "ocaml" 14 "jsont" {>= "0.2.0"} 15 "odoc" {with-doc} 16 ] ··· 28 "@doc" {with-doc} 29 ] 30 ] 31 + x-maintenance-intent: ["(latest)"]
+123 -173
src/jsont_pointer.ml
··· 1 (*--------------------------------------------------------------------------- 2 - Copyright (c) 2024 The jsont programmers. All rights reserved. 3 - SPDX-License-Identifier: ISC 4 - ---------------------------------------------------------------------------*) 5 6 (* Token escaping/unescaping per RFC 6901 Section 3-4 *) 7 module Token = struct ··· 167 168 let of_string_result s = 169 try Ok (of_string s) 170 - with Jsont.Error (_, _, _) as e -> 171 - Error (Jsont.Error.to_string (match e with Jsont.Error e -> e | _ -> assert false)) 172 173 (* URI fragment percent-decoding *) 174 let hex_value c = ··· 204 205 let of_uri_fragment_result s = 206 try Ok (of_uri_fragment s) 207 - with Jsont.Error (_, _, _) as e -> 208 - Error (Jsont.Error.to_string (match e with Jsont.Error e -> e | _ -> assert false)) 209 210 (* Serialization *) 211 ··· 272 | Segment.End, Segment.End -> 0 273 274 let equal p1 p2 = 275 - List.length p1 = List.length p2 && 276 - List.for_all2 segment_equal p1 p2 277 278 let compare p1 p2 = 279 - let rec loop l1 l2 = match l1, l2 with 280 - | [], [] -> 0 281 - | [], _ -> -1 282 - | _, [] -> 1 283 - | h1 :: t1, h2 :: t2 -> 284 - let c = segment_compare h1 h2 in 285 - if c <> 0 then c else loop t1 t2 286 - in 287 - loop p1 p2 288 289 (* Path conversion *) 290 ··· 384 (* Mutation helpers *) 385 386 let set_member name value (obj : Jsont.object') : Jsont.object' = 387 - let found = ref false in 388 - let result = List.map (fun ((n, m), v) -> 389 - if String.equal n name then begin 390 - found := true; 391 - ((n, m), value) 392 - end else 393 - ((n, m), v) 394 - ) obj in 395 - if !found then result 396 - else obj @ [((name, Jsont.Meta.none), value)] 397 398 let remove_member name (obj : Jsont.object') : Jsont.object' = 399 List.filter (fun ((n, _), _) -> not (String.equal n name)) obj 400 401 let insert_at n value lst = 402 let rec loop i acc = function 403 - | [] when i = n -> List.rev (value :: acc) 404 - | [] -> List.rev acc (* shouldn't happen if n is valid *) 405 - | h :: t when i = n -> List.rev_append (value :: acc) (h :: t) 406 | h :: t -> loop (i + 1) (h :: acc) t 407 in 408 loop 0 [] lst 409 410 let remove_at n lst = 411 - let rec loop i acc = function 412 - | [] -> List.rev acc 413 - | _ :: t when i = n -> List.rev_append acc t 414 - | h :: t -> loop (i + 1) (h :: acc) t 415 - in 416 - loop 0 [] lst 417 418 let replace_at n value lst = 419 List.mapi (fun i v -> if i = n then value else v) lst 420 421 (* Mutation: set *) 422 423 let rec eval_set p value json = ··· 425 | [] -> value 426 | [Segment.End] -> 427 (match json with 428 - | Jsont.Array (elements, meta) -> 429 - Jsont.Array (elements @ [value], meta) 430 | _ -> 431 Jsont.Error.msgf (Jsont.Json.meta json) 432 "JSON Pointer: '-' can only be used on arrays, got %s" ··· 435 Jsont.Error.msgf (Jsont.Json.meta json) 436 "JSON Pointer: '-' (end marker) refers to nonexistent array element" 437 | [Segment.Token token] -> 438 - (match json with 439 - | Jsont.Object (members, meta) -> 440 - if Option.is_some (get_member token members) then 441 - Jsont.Object (set_member token value members, meta) 442 - else 443 - Jsont.Error.msgf (Jsont.Json.meta json) 444 - "JSON Pointer: member '%s' not found for set" token 445 - | Jsont.Array (elements, meta) -> 446 - (match Token.is_valid_array_index token with 447 - | Some n when n >= 0 && n < List.length elements -> 448 Jsont.Array (replace_at n value elements, meta) 449 - | Some n -> 450 - Jsont.Error.msgf (Jsont.Json.meta json) 451 - "JSON Pointer: index %d out of bounds for set" n 452 - | None -> 453 Jsont.Error.msgf (Jsont.Json.meta json) 454 - "JSON Pointer: invalid array index '%s'" token) 455 - | _ -> 456 - Jsont.Error.msgf (Jsont.Json.meta json) 457 - "JSON Pointer: cannot set in %s" (json_sort_string json)) 458 | Segment.Token token :: rest -> 459 - (match json with 460 - | Jsont.Object (members, meta) -> 461 - (match get_member token members with 462 | Some (_, child) -> 463 Jsont.Object (set_member token (eval_set rest value child) members, meta) 464 - | None -> 465 - Jsont.Error.msgf (Jsont.Json.meta json) 466 - "JSON Pointer: member '%s' not found" token) 467 - | Jsont.Array (elements, meta) -> 468 - (match Token.is_valid_array_index token with 469 - | Some n -> 470 - (match get_nth n elements with 471 - | Some child -> 472 - Jsont.Array (replace_at n (eval_set rest value child) elements, meta) 473 - | None -> 474 - Jsont.Error.msgf (Jsont.Json.meta json) 475 - "JSON Pointer: index %d out of bounds" n) 476 - | None -> 477 - Jsont.Error.msgf (Jsont.Json.meta json) 478 - "JSON Pointer: invalid array index '%s'" token) 479 - | _ -> 480 - Jsont.Error.msgf (Jsont.Json.meta json) 481 - "JSON Pointer: cannot navigate through %s" (json_sort_string json)) 482 483 let set p json ~value = eval_set p value json 484 ··· 489 | [] -> value 490 | [Segment.End] -> 491 (match json with 492 - | Jsont.Array (elements, meta) -> 493 - Jsont.Array (elements @ [value], meta) 494 | _ -> 495 Jsont.Error.msgf (Jsont.Json.meta json) 496 "JSON Pointer: '-' can only be used on arrays, got %s" ··· 499 Jsont.Error.msgf (Jsont.Json.meta json) 500 "JSON Pointer: '-' in non-final position" 501 | [Segment.Token token] -> 502 - (match json with 503 - | Jsont.Object (members, meta) -> 504 - (* For objects, add/replace member *) 505 - Jsont.Object (set_member token value members, meta) 506 - | Jsont.Array (elements, meta) -> 507 - (* For arrays, insert at index *) 508 - (match Token.is_valid_array_index token with 509 - | Some n -> 510 - let len = List.length elements in 511 - if n >= 0 && n <= len then 512 - Jsont.Array (insert_at n value elements, meta) 513 - else 514 - Jsont.Error.msgf (Jsont.Json.meta json) 515 - "JSON Pointer: index %d out of bounds for add (array has %d elements)" 516 - n len 517 - | None -> 518 Jsont.Error.msgf (Jsont.Json.meta json) 519 - "JSON Pointer: invalid array index '%s'" token) 520 - | _ -> 521 - Jsont.Error.msgf (Jsont.Json.meta json) 522 - "JSON Pointer: cannot add to %s" (json_sort_string json)) 523 | Segment.Token token :: rest -> 524 - (match json with 525 - | Jsont.Object (members, meta) -> 526 - (match get_member token members with 527 | Some (_, child) -> 528 Jsont.Object (set_member token (eval_add rest value child) members, meta) 529 - | None -> 530 - Jsont.Error.msgf (Jsont.Json.meta json) 531 - "JSON Pointer: member '%s' not found" token) 532 - | Jsont.Array (elements, meta) -> 533 - (match Token.is_valid_array_index token with 534 - | Some n -> 535 - (match get_nth n elements with 536 - | Some child -> 537 - Jsont.Array (replace_at n (eval_add rest value child) elements, meta) 538 - | None -> 539 - Jsont.Error.msgf (Jsont.Json.meta json) 540 - "JSON Pointer: index %d out of bounds" n) 541 - | None -> 542 - Jsont.Error.msgf (Jsont.Json.meta json) 543 - "JSON Pointer: invalid array index '%s'" token) 544 - | _ -> 545 - Jsont.Error.msgf (Jsont.Json.meta json) 546 - "JSON Pointer: cannot navigate through %s" (json_sort_string json)) 547 548 let add p json ~value = eval_add p value json 549 ··· 552 let rec eval_remove p json = 553 match p with 554 | [] -> 555 - Jsont.Error.msgf Jsont.Meta.none 556 - "JSON Pointer: cannot remove root document" 557 | [Segment.End] -> 558 Jsont.Error.msgf (Jsont.Json.meta json) 559 "JSON Pointer: '-' refers to nonexistent element" ··· 561 Jsont.Error.msgf (Jsont.Json.meta json) 562 "JSON Pointer: '-' in non-final position" 563 | [Segment.Token token] -> 564 - (match json with 565 - | Jsont.Object (members, meta) -> 566 - if Option.is_some (get_member token members) then 567 - Jsont.Object (remove_member token members, meta) 568 - else 569 - Jsont.Error.msgf (Jsont.Json.meta json) 570 - "JSON Pointer: member '%s' not found for remove" token 571 - | Jsont.Array (elements, meta) -> 572 - (match Token.is_valid_array_index token with 573 - | Some n when n >= 0 && n < List.length elements -> 574 Jsont.Array (remove_at n elements, meta) 575 - | Some n -> 576 Jsont.Error.msgf (Jsont.Json.meta json) 577 - "JSON Pointer: index %d out of bounds for remove" n 578 - | None -> 579 - Jsont.Error.msgf (Jsont.Json.meta json) 580 - "JSON Pointer: invalid array index '%s'" token) 581 - | _ -> 582 - Jsont.Error.msgf (Jsont.Json.meta json) 583 - "JSON Pointer: cannot remove from %s" (json_sort_string json)) 584 | Segment.Token token :: rest -> 585 - (match json with 586 - | Jsont.Object (members, meta) -> 587 - (match get_member token members with 588 | Some (_, child) -> 589 Jsont.Object (set_member token (eval_remove rest child) members, meta) 590 - | None -> 591 - Jsont.Error.msgf (Jsont.Json.meta json) 592 - "JSON Pointer: member '%s' not found" token) 593 - | Jsont.Array (elements, meta) -> 594 - (match Token.is_valid_array_index token with 595 - | Some n -> 596 - (match get_nth n elements with 597 - | Some child -> 598 - Jsont.Array (replace_at n (eval_remove rest child) elements, meta) 599 - | None -> 600 - Jsont.Error.msgf (Jsont.Json.meta json) 601 - "JSON Pointer: index %d out of bounds" n) 602 - | None -> 603 - Jsont.Error.msgf (Jsont.Json.meta json) 604 - "JSON Pointer: invalid array index '%s'" token) 605 - | _ -> 606 - Jsont.Error.msgf (Jsont.Json.meta json) 607 - "JSON Pointer: cannot navigate through %s" (json_sort_string json)) 608 609 let remove p json = eval_remove p json 610 ··· 617 618 (* Mutation: move *) 619 620 - let is_prefix_of p1 p2 = 621 - let rec loop l1 l2 = match l1, l2 with 622 - | [], _ -> true 623 - | _, [] -> false 624 - | h1 :: t1, h2 :: t2 -> 625 - segment_equal h1 h2 && loop t1 t2 626 - in 627 - loop p1 p2 628 629 let move ~from ~path json = 630 (* Check for cycle: path cannot be a proper prefix of from *) ··· 644 (* Mutation: test *) 645 646 let test p json ~expected = 647 - match find p json with 648 - | None -> false 649 - | Some value -> Jsont.Json.equal value expected 650 651 (* Jsont codec *) 652
··· 1 (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 6 (* Token escaping/unescaping per RFC 6901 Section 3-4 *) 7 module Token = struct ··· 167 168 let of_string_result s = 169 try Ok (of_string s) 170 + with Jsont.Error e -> Error (Jsont.Error.to_string e) 171 172 (* URI fragment percent-decoding *) 173 let hex_value c = ··· 203 204 let of_uri_fragment_result s = 205 try Ok (of_uri_fragment s) 206 + with Jsont.Error e -> Error (Jsont.Error.to_string e) 207 208 (* Serialization *) 209 ··· 270 | Segment.End, Segment.End -> 0 271 272 let equal p1 p2 = 273 + List.equal segment_equal p1 p2 274 275 let compare p1 p2 = 276 + List.compare segment_compare p1 p2 277 278 (* Path conversion *) 279 ··· 373 (* Mutation helpers *) 374 375 let set_member name value (obj : Jsont.object') : Jsont.object' = 376 + let rec loop found acc = function 377 + | [] -> 378 + if found then List.rev acc 379 + else List.rev_append acc [((name, Jsont.Meta.none), value)] 380 + | ((n, m), _) :: rest when String.equal n name -> 381 + loop true (((n, m), value) :: acc) rest 382 + | mem :: rest -> 383 + loop found (mem :: acc) rest 384 + in 385 + loop false [] obj 386 387 let remove_member name (obj : Jsont.object') : Jsont.object' = 388 List.filter (fun ((n, _), _) -> not (String.equal n name)) obj 389 390 let insert_at n value lst = 391 let rec loop i acc = function 392 + | rest when i = n -> List.rev_append acc (value :: rest) 393 + | [] -> List.rev acc 394 | h :: t -> loop (i + 1) (h :: acc) t 395 in 396 loop 0 [] lst 397 398 let remove_at n lst = 399 + List.filteri (fun i _ -> i <> n) lst 400 401 let replace_at n value lst = 402 List.mapi (fun i v -> if i = n then value else v) lst 403 404 + (* Common navigation for mutation operations *) 405 + 406 + let navigate_to_child token json ~on_object ~on_array ~on_other = 407 + match json with 408 + | Jsont.Object (members, meta) -> on_object members meta 409 + | Jsont.Array (elements, meta) -> 410 + (match Token.is_valid_array_index token with 411 + | Some n -> on_array elements meta n 412 + | None -> 413 + Jsont.Error.msgf (Jsont.Json.meta json) 414 + "JSON Pointer: invalid array index '%s'" token) 415 + | _ -> on_other () 416 + 417 + let error_member_not_found json token = 418 + Jsont.Error.msgf (Jsont.Json.meta json) "JSON Pointer: member '%s' not found" token 419 + 420 + let error_index_out_of_bounds json n = 421 + Jsont.Error.msgf (Jsont.Json.meta json) "JSON Pointer: index %d out of bounds" n 422 + 423 + let error_cannot_navigate json = 424 + Jsont.Error.msgf (Jsont.Json.meta json) 425 + "JSON Pointer: cannot navigate through %s" (json_sort_string json) 426 + 427 (* Mutation: set *) 428 429 let rec eval_set p value json = ··· 431 | [] -> value 432 | [Segment.End] -> 433 (match json with 434 + | Jsont.Array (elements, meta) -> Jsont.Array (elements @ [value], meta) 435 | _ -> 436 Jsont.Error.msgf (Jsont.Json.meta json) 437 "JSON Pointer: '-' can only be used on arrays, got %s" ··· 440 Jsont.Error.msgf (Jsont.Json.meta json) 441 "JSON Pointer: '-' (end marker) refers to nonexistent array element" 442 | [Segment.Token token] -> 443 + navigate_to_child token json 444 + ~on_object:(fun members meta -> 445 + if Option.is_some (get_member token members) then 446 + Jsont.Object (set_member token value members, meta) 447 + else 448 + Jsont.Error.msgf (Jsont.Json.meta json) 449 + "JSON Pointer: member '%s' not found for set" token) 450 + ~on_array:(fun elements meta n -> 451 + if n < List.length elements then 452 Jsont.Array (replace_at n value elements, meta) 453 + else 454 Jsont.Error.msgf (Jsont.Json.meta json) 455 + "JSON Pointer: index %d out of bounds for set" n) 456 + ~on_other:(fun () -> 457 + Jsont.Error.msgf (Jsont.Json.meta json) 458 + "JSON Pointer: cannot set in %s" (json_sort_string json)) 459 | Segment.Token token :: rest -> 460 + navigate_to_child token json 461 + ~on_object:(fun members meta -> 462 + match get_member token members with 463 | Some (_, child) -> 464 Jsont.Object (set_member token (eval_set rest value child) members, meta) 465 + | None -> error_member_not_found json token) 466 + ~on_array:(fun elements meta n -> 467 + match get_nth n elements with 468 + | Some child -> 469 + Jsont.Array (replace_at n (eval_set rest value child) elements, meta) 470 + | None -> error_index_out_of_bounds json n) 471 + ~on_other:(fun () -> error_cannot_navigate json) 472 473 let set p json ~value = eval_set p value json 474 ··· 479 | [] -> value 480 | [Segment.End] -> 481 (match json with 482 + | Jsont.Array (elements, meta) -> Jsont.Array (elements @ [value], meta) 483 | _ -> 484 Jsont.Error.msgf (Jsont.Json.meta json) 485 "JSON Pointer: '-' can only be used on arrays, got %s" ··· 488 Jsont.Error.msgf (Jsont.Json.meta json) 489 "JSON Pointer: '-' in non-final position" 490 | [Segment.Token token] -> 491 + navigate_to_child token json 492 + ~on_object:(fun members meta -> 493 + Jsont.Object (set_member token value members, meta)) 494 + ~on_array:(fun elements meta n -> 495 + let len = List.length elements in 496 + if n <= len then 497 + Jsont.Array (insert_at n value elements, meta) 498 + else 499 Jsont.Error.msgf (Jsont.Json.meta json) 500 + "JSON Pointer: index %d out of bounds for add (array has %d elements)" 501 + n len) 502 + ~on_other:(fun () -> 503 + Jsont.Error.msgf (Jsont.Json.meta json) 504 + "JSON Pointer: cannot add to %s" (json_sort_string json)) 505 | Segment.Token token :: rest -> 506 + navigate_to_child token json 507 + ~on_object:(fun members meta -> 508 + match get_member token members with 509 | Some (_, child) -> 510 Jsont.Object (set_member token (eval_add rest value child) members, meta) 511 + | None -> error_member_not_found json token) 512 + ~on_array:(fun elements meta n -> 513 + match get_nth n elements with 514 + | Some child -> 515 + Jsont.Array (replace_at n (eval_add rest value child) elements, meta) 516 + | None -> error_index_out_of_bounds json n) 517 + ~on_other:(fun () -> error_cannot_navigate json) 518 519 let add p json ~value = eval_add p value json 520 ··· 523 let rec eval_remove p json = 524 match p with 525 | [] -> 526 + Jsont.Error.msgf Jsont.Meta.none "JSON Pointer: cannot remove root document" 527 | [Segment.End] -> 528 Jsont.Error.msgf (Jsont.Json.meta json) 529 "JSON Pointer: '-' refers to nonexistent element" ··· 531 Jsont.Error.msgf (Jsont.Json.meta json) 532 "JSON Pointer: '-' in non-final position" 533 | [Segment.Token token] -> 534 + navigate_to_child token json 535 + ~on_object:(fun members meta -> 536 + if Option.is_some (get_member token members) then 537 + Jsont.Object (remove_member token members, meta) 538 + else 539 + Jsont.Error.msgf (Jsont.Json.meta json) 540 + "JSON Pointer: member '%s' not found for remove" token) 541 + ~on_array:(fun elements meta n -> 542 + if n < List.length elements then 543 Jsont.Array (remove_at n elements, meta) 544 + else 545 Jsont.Error.msgf (Jsont.Json.meta json) 546 + "JSON Pointer: index %d out of bounds for remove" n) 547 + ~on_other:(fun () -> 548 + Jsont.Error.msgf (Jsont.Json.meta json) 549 + "JSON Pointer: cannot remove from %s" (json_sort_string json)) 550 | Segment.Token token :: rest -> 551 + navigate_to_child token json 552 + ~on_object:(fun members meta -> 553 + match get_member token members with 554 | Some (_, child) -> 555 Jsont.Object (set_member token (eval_remove rest child) members, meta) 556 + | None -> error_member_not_found json token) 557 + ~on_array:(fun elements meta n -> 558 + match get_nth n elements with 559 + | Some child -> 560 + Jsont.Array (replace_at n (eval_remove rest child) elements, meta) 561 + | None -> error_index_out_of_bounds json n) 562 + ~on_other:(fun () -> error_cannot_navigate json) 563 564 let remove p json = eval_remove p json 565 ··· 572 573 (* Mutation: move *) 574 575 + let rec is_prefix_of p1 p2 = 576 + match p1, p2 with 577 + | [], _ -> true 578 + | _, [] -> false 579 + | h1 :: t1, h2 :: t2 -> segment_equal h1 h2 && is_prefix_of t1 t2 580 581 let move ~from ~path json = 582 (* Check for cycle: path cannot be a proper prefix of from *) ··· 596 (* Mutation: test *) 597 598 let test p json ~expected = 599 + Option.fold ~none:false ~some:(Jsont.Json.equal expected) (find p json) 600 601 (* Jsont codec *) 602
+3 -3
src/jsont_pointer.mli
··· 1 (*--------------------------------------------------------------------------- 2 - Copyright (c) 2024 The jsont programmers. All rights reserved. 3 - SPDX-License-Identifier: ISC 4 - ---------------------------------------------------------------------------*) 5 6 (** RFC 6901 JSON Pointer implementation for jsont. 7
··· 1 (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 6 (** RFC 6901 JSON Pointer implementation for jsont. 7