crdt library in ocaml implementing json-joy
1(** Compact JSON codec for Patch.
2
3 Encodes/decodes patches in the json-joy compact JSON format. This is an
4 array-based encoding that is more compact than verbose:
5
6 Patch: [[id], op1, op2, ...] ID: [[sid, time]] Operations: [opcode, ...args]
7
8 Timestamps in operations can be encoded as just the time component when the
9 session ID matches the patch's session ID.
10
11 Compact opcodes (from json-joy):
12 - 0: new_con
13 - 1: new_val
14 - 2: new_obj
15 - 3: new_vec
16 - 4: new_str
17 - 5: new_bin
18 - 6: new_arr
19 - 9: ins_val
20 - 10: ins_obj
21 - 11: ins_vec
22 - 12: ins_str
23 - 13: ins_bin
24 - 14: ins_arr
25 - 15: upd_arr
26 - 16: del
27 - 17: nop *)
28
29module J = Simdjsont.Json
30
31let compact_opcode_of_op = function
32 | Op.Op_new_con _ -> 0
33 | Op.Op_new_val -> 1
34 | Op.Op_new_obj -> 2
35 | Op.Op_new_vec -> 3
36 | Op.Op_new_str -> 4
37 | Op.Op_new_bin -> 5
38 | Op.Op_new_arr -> 6
39 | Op.Op_ins_val _ -> 9
40 | Op.Op_ins_obj _ -> 10
41 | Op.Op_ins_vec _ -> 11
42 | Op.Op_ins_str _ -> 12
43 | Op.Op_ins_bin _ -> 13
44 | Op.Op_ins_arr _ -> 14
45 | Op.Op_upd_arr _ -> 15
46 | Op.Op_del _ -> 16
47 | Op.Op_nop _ -> 17
48
49let encode_ts ~patch_sid (ts : Clock.timestamp) : J.t =
50 if ts.sid = patch_sid then J.Float (Float.of_int ts.time)
51 else J.Array [ J.Float (Float.of_int ts.sid); J.Float (Float.of_int ts.time) ]
52
53let encode_timespan ~patch_sid (ts : Clock.timespan) : J.t =
54 if ts.sid = patch_sid then
55 J.Array [ J.Float (Float.of_int ts.time); J.Float (Float.of_int ts.span) ]
56 else
57 J.Array
58 [
59 J.Array
60 [ J.Float (Float.of_int ts.sid); J.Float (Float.of_int ts.time) ];
61 J.Float (Float.of_int ts.span);
62 ]
63
64let encode_con_value (v : Value.t) : J.t =
65 match v with
66 | Value.Null -> J.Null
67 | Value.Bool b -> J.Bool b
68 | Value.Int n -> J.Float (Float.of_int n)
69 | Value.Float f -> J.Float f
70 | Value.String s -> J.String s
71 | _ -> J.Null
72
73let encode_op ~patch_sid (op : Op.op_data) : J.t =
74 let opcode = J.Float (Float.of_int (compact_opcode_of_op op)) in
75 match op with
76 | Op.Op_new_con { con_value } ->
77 J.Array [ opcode; encode_con_value con_value ]
78 | Op.Op_new_val | Op.Op_new_obj | Op.Op_new_vec | Op.Op_new_str
79 | Op.Op_new_bin | Op.Op_new_arr ->
80 J.Array [ opcode ]
81 | Op.Op_ins_val { ins_val_obj; ins_val_value } ->
82 J.Array
83 [
84 opcode;
85 encode_ts ~patch_sid ins_val_obj;
86 encode_ts ~patch_sid ins_val_value;
87 ]
88 | Op.Op_ins_obj { ins_obj_obj; ins_obj_value } ->
89 let entries =
90 List.map
91 (fun (key, ts) -> J.Array [ J.String key; encode_ts ~patch_sid ts ])
92 ins_obj_value
93 in
94 J.Array [ opcode; encode_ts ~patch_sid ins_obj_obj; J.Array entries ]
95 | Op.Op_ins_vec { ins_vec_obj; ins_vec_idx; ins_vec_value } ->
96 J.Array
97 [
98 opcode;
99 encode_ts ~patch_sid ins_vec_obj;
100 J.Float (Float.of_int ins_vec_idx);
101 encode_ts ~patch_sid ins_vec_value;
102 ]
103 | Op.Op_ins_str { ins_str_obj; ins_str_after; ins_str_value } ->
104 J.Array
105 [
106 opcode;
107 encode_ts ~patch_sid ins_str_obj;
108 encode_ts ~patch_sid ins_str_after;
109 J.String ins_str_value;
110 ]
111 | Op.Op_ins_bin { ins_bin_obj; ins_bin_after; ins_bin_value } ->
112 J.Array
113 [
114 opcode;
115 encode_ts ~patch_sid ins_bin_obj;
116 encode_ts ~patch_sid ins_bin_after;
117 J.String (Base64.encode_string (Bytes.to_string ins_bin_value));
118 ]
119 | Op.Op_ins_arr { ins_arr_obj; ins_arr_after; ins_arr_value } ->
120 J.Array
121 [
122 opcode;
123 encode_ts ~patch_sid ins_arr_obj;
124 encode_ts ~patch_sid ins_arr_after;
125 encode_ts ~patch_sid ins_arr_value;
126 ]
127 | Op.Op_upd_arr { upd_arr_obj; upd_arr_pos; upd_arr_value } ->
128 J.Array
129 [
130 opcode;
131 encode_ts ~patch_sid upd_arr_obj;
132 encode_ts ~patch_sid upd_arr_pos;
133 encode_ts ~patch_sid upd_arr_value;
134 ]
135 | Op.Op_del { del_obj; del_what } ->
136 let spans = List.map (encode_timespan ~patch_sid) del_what in
137 J.Array [ opcode; encode_ts ~patch_sid del_obj; J.Array spans ]
138 | Op.Op_nop { nop_len } ->
139 if nop_len = 1 then J.Array [ opcode ]
140 else J.Array [ opcode; J.Float (Float.of_int nop_len) ]
141
142let encode_patch_json (patch : Patch.t) : J.t =
143 let patch_sid = patch.id.sid in
144 let id_json =
145 J.Array
146 [
147 J.Array
148 [
149 J.Float (Float.of_int patch.id.sid);
150 J.Float (Float.of_int patch.id.time);
151 ];
152 ]
153 in
154 let ops_json = List.map (encode_op ~patch_sid) patch.ops in
155 J.Array (id_json :: ops_json)
156
157let encode (patch : Patch.t) : string =
158 let json = encode_patch_json patch in
159 J.to_string json
160
161let encode_pretty (patch : Patch.t) : string =
162 let json = encode_patch_json patch in
163 J.to_string json
164
165let decode_ts ~patch_sid (json : J.t) : Clock.timestamp option =
166 match json with
167 | J.Float time -> Some { sid = patch_sid; time = Float.to_int time }
168 | J.Int time -> Some { sid = patch_sid; time = Int64.to_int time }
169 | J.Array [ J.Float sid; J.Float time ] ->
170 Some { sid = Float.to_int sid; time = Float.to_int time }
171 | J.Array [ J.Int sid; J.Int time ] ->
172 Some { sid = Int64.to_int sid; time = Int64.to_int time }
173 | _ -> None
174
175let decode_timespan ~patch_sid (json : J.t) : Clock.timespan option =
176 match json with
177 | J.Array [ J.Float time; J.Float span ] ->
178 Some
179 { sid = patch_sid; time = Float.to_int time; span = Float.to_int span }
180 | J.Array [ J.Int time; J.Int span ] ->
181 Some
182 { sid = patch_sid; time = Int64.to_int time; span = Int64.to_int span }
183 | J.Array [ J.Array [ J.Float sid; J.Float time ]; J.Float span ] ->
184 Some
185 {
186 sid = Float.to_int sid;
187 time = Float.to_int time;
188 span = Float.to_int span;
189 }
190 | J.Array [ J.Array [ J.Int sid; J.Int time ]; J.Int span ] ->
191 Some
192 {
193 sid = Int64.to_int sid;
194 time = Int64.to_int time;
195 span = Int64.to_int span;
196 }
197 | J.Array [ J.Float sid; J.Float time; J.Float span ] ->
198 Some
199 {
200 sid = Float.to_int sid;
201 time = Float.to_int time;
202 span = Float.to_int span;
203 }
204 | J.Array [ J.Int sid; J.Int time; J.Int span ] ->
205 Some
206 {
207 sid = Int64.to_int sid;
208 time = Int64.to_int time;
209 span = Int64.to_int span;
210 }
211 | _ -> None
212
213let decode_con_value (json : J.t) : Value.t =
214 match json with
215 | J.Null -> Value.Null
216 | J.Bool b -> Value.Bool b
217 | J.Float f ->
218 if Float.is_integer f then Value.Int (Float.to_int f) else Value.Float f
219 | J.Int i -> Value.Int (Int64.to_int i)
220 | J.String s -> Value.String s
221 | _ -> Value.Null
222
223let get_arr_elem arr idx =
224 if idx < List.length arr then Some (List.nth arr idx) else None
225
226let _ = get_arr_elem
227
228let decode_op ~patch_sid (json : J.t) : (Op.op_data, string) result =
229 match json with
230 | J.Array elems -> (
231 match elems with
232 | [] -> Error "empty operation array"
233 | J.Float opcode_f :: args -> (
234 let opcode = Float.to_int opcode_f in
235 match opcode with
236 | 0 -> (
237 match args with
238 | [] -> Ok (Op.Op_new_con { con_value = Value.Undefined })
239 | [ value_json ] ->
240 Ok (Op.Op_new_con { con_value = decode_con_value value_json })
241 | _ -> Error "new_con: unexpected arguments")
242 | 1 -> Ok Op.Op_new_val
243 | 2 -> Ok Op.Op_new_obj
244 | 3 -> Ok Op.Op_new_vec
245 | 4 -> Ok Op.Op_new_str
246 | 5 -> Ok Op.Op_new_bin
247 | 6 -> Ok Op.Op_new_arr
248 | 9 -> (
249 match args with
250 | [ obj_json; value_json ] -> (
251 match
252 ( decode_ts ~patch_sid obj_json,
253 decode_ts ~patch_sid value_json )
254 with
255 | Some obj, Some value ->
256 Ok
257 (Op.Op_ins_val
258 { ins_val_obj = obj; ins_val_value = value })
259 | _ -> Error "ins_val: invalid timestamp")
260 | _ -> Error "ins_val: expected obj, value")
261 | 10 -> (
262 match args with
263 | [ obj_json; J.Array entries ] -> (
264 match decode_ts ~patch_sid obj_json with
265 | Some obj ->
266 let decode_entry json =
267 match json with
268 | J.Array [ J.String key; ts_json ] -> (
269 match decode_ts ~patch_sid ts_json with
270 | Some ts -> Some (key, ts)
271 | None -> None)
272 | _ -> None
273 in
274 let decoded = List.filter_map decode_entry entries in
275 if List.length decoded = List.length entries then
276 Ok
277 (Op.Op_ins_obj
278 { ins_obj_obj = obj; ins_obj_value = decoded })
279 else Error "ins_obj: invalid entry format"
280 | None -> Error "ins_obj: invalid obj timestamp")
281 | _ -> Error "ins_obj: expected obj, entries")
282 | 11 -> (
283 match args with
284 | [ obj_json; J.Float idx_f; value_json ] -> (
285 match
286 ( decode_ts ~patch_sid obj_json,
287 decode_ts ~patch_sid value_json )
288 with
289 | Some obj, Some value ->
290 Ok
291 (Op.Op_ins_vec
292 {
293 ins_vec_obj = obj;
294 ins_vec_idx = Float.to_int idx_f;
295 ins_vec_value = value;
296 })
297 | _ -> Error "ins_vec: invalid timestamp")
298 | [ obj_json; J.Int idx_i; value_json ] -> (
299 match
300 ( decode_ts ~patch_sid obj_json,
301 decode_ts ~patch_sid value_json )
302 with
303 | Some obj, Some value ->
304 Ok
305 (Op.Op_ins_vec
306 {
307 ins_vec_obj = obj;
308 ins_vec_idx = Int64.to_int idx_i;
309 ins_vec_value = value;
310 })
311 | _ -> Error "ins_vec: invalid timestamp")
312 | _ -> Error "ins_vec: expected obj, idx, value")
313 | 12 -> (
314 match args with
315 | [ obj_json; after_json; J.String value ] -> (
316 match
317 ( decode_ts ~patch_sid obj_json,
318 decode_ts ~patch_sid after_json )
319 with
320 | Some obj, Some after ->
321 Ok
322 (Op.Op_ins_str
323 {
324 ins_str_obj = obj;
325 ins_str_after = after;
326 ins_str_value = value;
327 })
328 | _ -> Error "ins_str: invalid timestamp")
329 | _ -> Error "ins_str: expected obj, after, value")
330 | 13 -> (
331 match args with
332 | [ obj_json; after_json; J.String b64_value ] -> (
333 match
334 ( decode_ts ~patch_sid obj_json,
335 decode_ts ~patch_sid after_json )
336 with
337 | Some obj, Some after -> (
338 match Base64.decode b64_value with
339 | Ok decoded ->
340 Ok
341 (Op.Op_ins_bin
342 {
343 ins_bin_obj = obj;
344 ins_bin_after = after;
345 ins_bin_value = Bytes.of_string decoded;
346 })
347 | Error _ -> Error "ins_bin: invalid base64")
348 | _ -> Error "ins_bin: invalid timestamp")
349 | _ -> Error "ins_bin: expected obj, after, value")
350 | 14 -> (
351 match args with
352 | [ obj_json; after_json; J.Array values_json ] -> (
353 match
354 ( decode_ts ~patch_sid obj_json,
355 decode_ts ~patch_sid after_json )
356 with
357 | Some obj, Some after -> (
358 match values_json with
359 | [ first_value ] -> (
360 match decode_ts ~patch_sid first_value with
361 | Some value ->
362 Ok
363 (Op.Op_ins_arr
364 {
365 ins_arr_obj = obj;
366 ins_arr_after = after;
367 ins_arr_value = value;
368 })
369 | None -> Error "ins_arr: invalid value timestamp")
370 | _ -> (
371 let decoded_values =
372 List.filter_map (decode_ts ~patch_sid) values_json
373 in
374 match decoded_values with
375 | value :: _ ->
376 Ok
377 (Op.Op_ins_arr
378 {
379 ins_arr_obj = obj;
380 ins_arr_after = after;
381 ins_arr_value = value;
382 })
383 | [] -> Error "ins_arr: no valid value timestamps"))
384 | _ -> Error "ins_arr: invalid obj/after timestamp")
385 | [ obj_json; after_json; value_json ] -> (
386 match
387 ( decode_ts ~patch_sid obj_json,
388 decode_ts ~patch_sid after_json,
389 decode_ts ~patch_sid value_json )
390 with
391 | Some obj, Some after, Some value ->
392 Ok
393 (Op.Op_ins_arr
394 {
395 ins_arr_obj = obj;
396 ins_arr_after = after;
397 ins_arr_value = value;
398 })
399 | _ -> Error "ins_arr: invalid timestamp")
400 | _ -> Error "ins_arr: expected obj, after, value")
401 | 15 -> (
402 match args with
403 | [ obj_json; pos_json; value_json ] -> (
404 match
405 ( decode_ts ~patch_sid obj_json,
406 decode_ts ~patch_sid pos_json,
407 decode_ts ~patch_sid value_json )
408 with
409 | Some obj, Some pos, Some value ->
410 Ok
411 (Op.Op_upd_arr
412 {
413 upd_arr_obj = obj;
414 upd_arr_pos = pos;
415 upd_arr_value = value;
416 })
417 | _ -> Error "upd_arr: invalid timestamp")
418 | _ -> Error "upd_arr: expected obj, pos, value")
419 | 16 -> (
420 match args with
421 | [ obj_json; J.Array spans_json ] -> (
422 match decode_ts ~patch_sid obj_json with
423 | Some obj ->
424 let decoded =
425 List.filter_map (decode_timespan ~patch_sid) spans_json
426 in
427 if List.length decoded = List.length spans_json then
428 Ok (Op.Op_del { del_obj = obj; del_what = decoded })
429 else Error "del: invalid timespan format"
430 | None -> Error "del: invalid obj timestamp")
431 | _ -> Error "del: expected obj, spans")
432 | 17 -> (
433 match args with
434 | [] -> Ok (Op.Op_nop { nop_len = 1 })
435 | [ J.Float len ] -> Ok (Op.Op_nop { nop_len = Float.to_int len })
436 | [ J.Int len ] -> Ok (Op.Op_nop { nop_len = Int64.to_int len })
437 | _ -> Error "nop: invalid format")
438 | _ -> Error (Printf.sprintf "unknown compact opcode: %d" opcode))
439 | J.Int opcode_i :: args -> (
440 let opcode = Int64.to_int opcode_i in
441 match opcode with
442 | 0 -> (
443 match args with
444 | [] -> Ok (Op.Op_new_con { con_value = Value.Undefined })
445 | [ value_json ] ->
446 Ok (Op.Op_new_con { con_value = decode_con_value value_json })
447 | _ -> Error "new_con: unexpected arguments")
448 | 1 -> Ok Op.Op_new_val
449 | 2 -> Ok Op.Op_new_obj
450 | 3 -> Ok Op.Op_new_vec
451 | 4 -> Ok Op.Op_new_str
452 | 5 -> Ok Op.Op_new_bin
453 | 6 -> Ok Op.Op_new_arr
454 | 9 -> (
455 match args with
456 | [ obj_json; value_json ] -> (
457 match
458 ( decode_ts ~patch_sid obj_json,
459 decode_ts ~patch_sid value_json )
460 with
461 | Some obj, Some value ->
462 Ok
463 (Op.Op_ins_val
464 { ins_val_obj = obj; ins_val_value = value })
465 | _ -> Error "ins_val: invalid timestamp")
466 | _ -> Error "ins_val: expected obj, value")
467 | 10 -> (
468 match args with
469 | [ obj_json; J.Array entries ] -> (
470 match decode_ts ~patch_sid obj_json with
471 | Some obj ->
472 let decode_entry json =
473 match json with
474 | J.Array [ J.String key; ts_json ] -> (
475 match decode_ts ~patch_sid ts_json with
476 | Some ts -> Some (key, ts)
477 | None -> None)
478 | _ -> None
479 in
480 let decoded = List.filter_map decode_entry entries in
481 if List.length decoded = List.length entries then
482 Ok
483 (Op.Op_ins_obj
484 { ins_obj_obj = obj; ins_obj_value = decoded })
485 else Error "ins_obj: invalid entry format"
486 | None -> Error "ins_obj: invalid obj timestamp")
487 | _ -> Error "ins_obj: expected obj, entries")
488 | 11 -> (
489 match args with
490 | [ obj_json; J.Float idx_f; value_json ] -> (
491 match
492 ( decode_ts ~patch_sid obj_json,
493 decode_ts ~patch_sid value_json )
494 with
495 | Some obj, Some value ->
496 Ok
497 (Op.Op_ins_vec
498 {
499 ins_vec_obj = obj;
500 ins_vec_idx = Float.to_int idx_f;
501 ins_vec_value = value;
502 })
503 | _ -> Error "ins_vec: invalid timestamp")
504 | [ obj_json; J.Int idx_i; value_json ] -> (
505 match
506 ( decode_ts ~patch_sid obj_json,
507 decode_ts ~patch_sid value_json )
508 with
509 | Some obj, Some value ->
510 Ok
511 (Op.Op_ins_vec
512 {
513 ins_vec_obj = obj;
514 ins_vec_idx = Int64.to_int idx_i;
515 ins_vec_value = value;
516 })
517 | _ -> Error "ins_vec: invalid timestamp")
518 | _ -> Error "ins_vec: expected obj, idx, value")
519 | 12 -> (
520 match args with
521 | [ obj_json; after_json; J.String value ] -> (
522 match
523 ( decode_ts ~patch_sid obj_json,
524 decode_ts ~patch_sid after_json )
525 with
526 | Some obj, Some after ->
527 Ok
528 (Op.Op_ins_str
529 {
530 ins_str_obj = obj;
531 ins_str_after = after;
532 ins_str_value = value;
533 })
534 | _ -> Error "ins_str: invalid timestamp")
535 | _ -> Error "ins_str: expected obj, after, value")
536 | 13 -> (
537 match args with
538 | [ obj_json; after_json; J.String b64_value ] -> (
539 match
540 ( decode_ts ~patch_sid obj_json,
541 decode_ts ~patch_sid after_json )
542 with
543 | Some obj, Some after -> (
544 match Base64.decode b64_value with
545 | Ok decoded ->
546 Ok
547 (Op.Op_ins_bin
548 {
549 ins_bin_obj = obj;
550 ins_bin_after = after;
551 ins_bin_value = Bytes.of_string decoded;
552 })
553 | Error _ -> Error "ins_bin: invalid base64")
554 | _ -> Error "ins_bin: invalid timestamp")
555 | _ -> Error "ins_bin: expected obj, after, value")
556 | 14 -> (
557 match args with
558 | [ obj_json; after_json; J.Array values_json ] -> (
559 match
560 ( decode_ts ~patch_sid obj_json,
561 decode_ts ~patch_sid after_json )
562 with
563 | Some obj, Some after -> (
564 match values_json with
565 | [ first_value ] -> (
566 match decode_ts ~patch_sid first_value with
567 | Some value ->
568 Ok
569 (Op.Op_ins_arr
570 {
571 ins_arr_obj = obj;
572 ins_arr_after = after;
573 ins_arr_value = value;
574 })
575 | None -> Error "ins_arr: invalid value timestamp")
576 | _ -> (
577 let decoded_values =
578 List.filter_map (decode_ts ~patch_sid) values_json
579 in
580 match decoded_values with
581 | value :: _ ->
582 Ok
583 (Op.Op_ins_arr
584 {
585 ins_arr_obj = obj;
586 ins_arr_after = after;
587 ins_arr_value = value;
588 })
589 | [] -> Error "ins_arr: no valid value timestamps"))
590 | _ -> Error "ins_arr: invalid obj/after timestamp")
591 | [ obj_json; after_json; value_json ] -> (
592 match
593 ( decode_ts ~patch_sid obj_json,
594 decode_ts ~patch_sid after_json,
595 decode_ts ~patch_sid value_json )
596 with
597 | Some obj, Some after, Some value ->
598 Ok
599 (Op.Op_ins_arr
600 {
601 ins_arr_obj = obj;
602 ins_arr_after = after;
603 ins_arr_value = value;
604 })
605 | _ -> Error "ins_arr: invalid timestamp")
606 | _ -> Error "ins_arr: expected obj, after, value")
607 | 15 -> (
608 match args with
609 | [ obj_json; pos_json; value_json ] -> (
610 match
611 ( decode_ts ~patch_sid obj_json,
612 decode_ts ~patch_sid pos_json,
613 decode_ts ~patch_sid value_json )
614 with
615 | Some obj, Some pos, Some value ->
616 Ok
617 (Op.Op_upd_arr
618 {
619 upd_arr_obj = obj;
620 upd_arr_pos = pos;
621 upd_arr_value = value;
622 })
623 | _ -> Error "upd_arr: invalid timestamp")
624 | _ -> Error "upd_arr: expected obj, pos, value")
625 | 16 -> (
626 match args with
627 | [ obj_json; J.Array spans_json ] -> (
628 match decode_ts ~patch_sid obj_json with
629 | Some obj ->
630 let decoded =
631 List.filter_map (decode_timespan ~patch_sid) spans_json
632 in
633 if List.length decoded = List.length spans_json then
634 Ok (Op.Op_del { del_obj = obj; del_what = decoded })
635 else Error "del: invalid timespan format"
636 | None -> Error "del: invalid obj timestamp")
637 | _ -> Error "del: expected obj, spans")
638 | 17 -> (
639 match args with
640 | [] -> Ok (Op.Op_nop { nop_len = 1 })
641 | [ J.Float len ] -> Ok (Op.Op_nop { nop_len = Float.to_int len })
642 | [ J.Int len ] -> Ok (Op.Op_nop { nop_len = Int64.to_int len })
643 | _ -> Error "nop: invalid format")
644 | _ -> Error (Printf.sprintf "unknown compact opcode: %d" opcode))
645 | _ -> Error "operation must start with opcode number")
646 | _ -> Error "operation must be an array"
647
648let decode_patch_json (json : J.t) : (Patch.t, string) result =
649 match json with
650 | J.Array elems -> (
651 match elems with
652 | [] -> Error "empty patch array"
653 | id_json :: ops_json -> (
654 match id_json with
655 | J.Array [ J.Array [ J.Float sid; J.Float time ] ] -> (
656 let id : Clock.timestamp =
657 { sid = Float.to_int sid; time = Float.to_int time }
658 in
659 let patch_sid = id.sid in
660 let rec decode_ops acc = function
661 | [] -> Ok (List.rev acc)
662 | op_json :: rest -> (
663 match decode_op ~patch_sid op_json with
664 | Ok op -> decode_ops (op :: acc) rest
665 | Error e -> Error e)
666 in
667 match decode_ops [] ops_json with
668 | Ok ops -> Ok (Patch.create ~id ~ops)
669 | Error e -> Error e)
670 | J.Array [ J.Array [ J.Int sid; J.Int time ] ] -> (
671 let id : Clock.timestamp =
672 { sid = Int64.to_int sid; time = Int64.to_int time }
673 in
674 let patch_sid = id.sid in
675 let rec decode_ops acc = function
676 | [] -> Ok (List.rev acc)
677 | op_json :: rest -> (
678 match decode_op ~patch_sid op_json with
679 | Ok op -> decode_ops (op :: acc) rest
680 | Error e -> Error e)
681 in
682 match decode_ops [] ops_json with
683 | Ok ops -> Ok (Patch.create ~id ~ops)
684 | Error e -> Error e)
685 | _ -> Error "invalid patch ID format"))
686 | _ -> Error "patch must be an array"
687
688let decode (s : string) : (Patch.t, string) result =
689 match Simdjsont.Decode.decode_string Simdjsont.Decode.value s with
690 | Ok json -> decode_patch_json json
691 | Error e -> Error (Printf.sprintf "JSON parse error: %s" e)
692
693let encode_batch_json (batch : Patch.batch) : J.t =
694 J.Array (List.map encode_patch_json batch)
695
696let encode_batch (batch : Patch.batch) : string =
697 let json = encode_batch_json batch in
698 J.to_string json
699
700let decode_batch_json (json : J.t) : (Patch.batch, string) result =
701 match json with
702 | J.Array patches_json ->
703 let rec decode_patches acc = function
704 | [] -> Ok (List.rev acc)
705 | patch_json :: rest -> (
706 match decode_patch_json patch_json with
707 | Ok patch -> decode_patches (patch :: acc) rest
708 | Error e -> Error e)
709 in
710 decode_patches [] patches_json
711 | _ -> Error "expected array of patches"
712
713let decode_batch (s : string) : (Patch.batch, string) result =
714 match Simdjsont.Decode.decode_string Simdjsont.Decode.value s with
715 | Ok json -> decode_batch_json json
716 | Error e -> Error (Printf.sprintf "JSON parse error: %s" e)