crdt library in ocaml implementing json-joy
at main 29 kB view raw
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)