+17
-1
.gitignore
+17
-1
.gitignore
+1
.ocamlformat
+1
.ocamlformat
···
···
1
+
version=0.28.1
+53
.tangled/workflows/build.yml
+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
+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
dune
···
···
1
+
(data_only_dirs third_party)
+13
-7
dune-project
+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
+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
+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
+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