crdt library in ocaml implementing json-joy
at main 13 kB view raw
1(** JSON codec for CRDT operations. 2 3 Encodes/decodes operations in the json-joy verbose JSON format. *) 4 5module J = Simdjsont.Json 6 7(** {1 Timestamp Codec} *) 8 9(** Encode a timestamp as [sid, time] JSON array *) 10let encode_timestamp (ts : Clock.timestamp) : J.t = 11 J.Array [ J.Float (Float.of_int ts.sid); J.Float (Float.of_int ts.time) ] 12 13(** Decode a timestamp from JSON *) 14let decode_timestamp (json : J.t) : Clock.timestamp option = 15 match json with 16 | J.Array [ J.Float sid; J.Float time ] -> 17 Some { sid = Float.to_int sid; time = Float.to_int time } 18 | J.Array [ J.Int sid; J.Int time ] -> 19 Some { sid = Int64.to_int sid; time = Int64.to_int time } 20 | J.Array [ J.Int sid; J.Float time ] -> 21 Some { sid = Int64.to_int sid; time = Float.to_int time } 22 | J.Array [ J.Float sid; J.Int time ] -> 23 Some { sid = Float.to_int sid; time = Int64.to_int time } 24 | _ -> None 25 26(** {1 Timespan Codec} *) 27 28(** Encode a timespan as [sid, time, span] JSON array *) 29let encode_timespan (ts : Clock.timespan) : J.t = 30 J.Array 31 [ 32 J.Float (Float.of_int ts.sid); 33 J.Float (Float.of_int ts.time); 34 J.Float (Float.of_int ts.span); 35 ] 36 37(** Decode a timespan from JSON *) 38let decode_timespan (json : J.t) : Clock.timespan option = 39 match json with 40 | J.Array [ J.Float sid; J.Float time; J.Float span ] -> 41 Some 42 { 43 sid = Float.to_int sid; 44 time = Float.to_int time; 45 span = Float.to_int span; 46 } 47 | J.Array [ J.Int sid; J.Int time; J.Int span ] -> 48 Some 49 { 50 sid = Int64.to_int sid; 51 time = Int64.to_int time; 52 span = Int64.to_int span; 53 } 54 | _ -> None 55 56(** {1 Object Entry Codec} *) 57 58(** Encode an object entry as ["key", [sid, time]] *) 59let encode_obj_entry ((key, ts) : Op.obj_entry) : J.t = 60 J.Array [ J.String key; encode_timestamp ts ] 61 62(** Decode an object entry from ["key", [sid, time]] *) 63let decode_obj_entry (json : J.t) : Op.obj_entry option = 64 match json with 65 | J.Array [ J.String key; ts_json ] -> ( 66 match decode_timestamp ts_json with 67 | Some ts -> Some (key, ts) 68 | None -> None) 69 | _ -> None 70 71(** {1 Value Codec for new_con} *) 72 73(** Encode a Value.t for new_con (only primitives: null, bool, number, string) 74*) 75let encode_con_value (v : Value.t) : J.t = 76 match v with 77 | Value.Null -> J.Null 78 | Value.Bool b -> J.Bool b 79 | Value.Int n -> J.Float (Float.of_int n) 80 | Value.Float f -> J.Float f 81 | Value.String s -> J.String s 82 | _ -> J.Null 83(* Unsupported values become null *) 84 85(** Decode a Value.t from JSON for new_con *) 86let decode_con_value (json : J.t) : Value.t = 87 match json with 88 | J.Null -> Value.Null 89 | J.Bool b -> Value.Bool b 90 | J.Float f -> 91 (* Check if it's an integer *) 92 if Float.is_integer f then Value.Int (Float.to_int f) else Value.Float f 93 | J.Int i -> Value.Int (Int64.to_int i) 94 | J.String s -> Value.String s 95 | _ -> Value.Null 96 97(** {1 Operation Encoding} *) 98 99(** Make a JSON object with the given members *) 100let make_obj members : J.t = J.Object members 101 102(** Encode an operation to JSON (verbose format) *) 103let encode_op (op : Op.op_data) : J.t = 104 let op_name = Op.op_name op in 105 let op_str = J.String op_name in 106 match op with 107 | Op_new_con { con_value } -> 108 make_obj [ ("op", op_str); ("value", encode_con_value con_value) ] 109 | Op_new_val | Op_new_obj | Op_new_vec | Op_new_str | Op_new_bin | Op_new_arr 110 -> 111 make_obj [ ("op", op_str) ] 112 | Op_ins_val { ins_val_obj; ins_val_value } -> 113 make_obj 114 [ 115 ("op", op_str); 116 ("obj", encode_timestamp ins_val_obj); 117 ("value", encode_timestamp ins_val_value); 118 ] 119 | Op_ins_obj { ins_obj_obj; ins_obj_value } -> 120 make_obj 121 [ 122 ("op", op_str); 123 ("obj", encode_timestamp ins_obj_obj); 124 ("value", J.Array (List.map encode_obj_entry ins_obj_value)); 125 ] 126 | Op_ins_vec { ins_vec_obj; ins_vec_idx; ins_vec_value } -> 127 make_obj 128 [ 129 ("op", op_str); 130 ("obj", encode_timestamp ins_vec_obj); 131 ("idx", J.Float (Float.of_int ins_vec_idx)); 132 ("value", encode_timestamp ins_vec_value); 133 ] 134 | Op_ins_str { ins_str_obj; ins_str_after; ins_str_value } -> 135 make_obj 136 [ 137 ("op", op_str); 138 ("obj", encode_timestamp ins_str_obj); 139 ("after", encode_timestamp ins_str_after); 140 ("value", J.String ins_str_value); 141 ] 142 | Op_ins_bin { ins_bin_obj; ins_bin_after; ins_bin_value } -> 143 (* Binary is base64 encoded *) 144 make_obj 145 [ 146 ("op", op_str); 147 ("obj", encode_timestamp ins_bin_obj); 148 ("after", encode_timestamp ins_bin_after); 149 ( "value", 150 J.String (Base64.encode_string (Bytes.to_string ins_bin_value)) ); 151 ] 152 | Op_ins_arr { ins_arr_obj; ins_arr_after; ins_arr_value } -> 153 make_obj 154 [ 155 ("op", op_str); 156 ("obj", encode_timestamp ins_arr_obj); 157 ("after", encode_timestamp ins_arr_after); 158 ("value", encode_timestamp ins_arr_value); 159 ] 160 | Op_upd_arr { upd_arr_obj; upd_arr_pos; upd_arr_value } -> 161 make_obj 162 [ 163 ("op", op_str); 164 ("obj", encode_timestamp upd_arr_obj); 165 ("pos", encode_timestamp upd_arr_pos); 166 ("value", encode_timestamp upd_arr_value); 167 ] 168 | Op_del { del_obj; del_what } -> 169 make_obj 170 [ 171 ("op", op_str); 172 ("obj", encode_timestamp del_obj); 173 ("what", J.Array (List.map encode_timespan del_what)); 174 ] 175 | Op_nop { nop_len } -> 176 make_obj [ ("op", op_str); ("len", J.Float (Float.of_int nop_len)) ] 177 178(** {1 Operation Decoding} *) 179 180(** Get a member from a JSON object members list *) 181let get_member key (mems : (string * J.t) list) : J.t option = 182 List.find_map (fun (k, v) -> if k = key then Some v else None) mems 183 184(** Get a string field from a JSON object *) 185let get_string key mems = 186 match get_member key mems with Some (J.String s) -> Some s | _ -> None 187 188(** Get a timestamp field from a JSON object *) 189let get_timestamp key mems = 190 match get_member key mems with 191 | Some json -> decode_timestamp json 192 | None -> None 193 194(** Get an int field from a JSON object *) 195let get_int key mems = 196 match get_member key mems with 197 | Some (J.Float f) -> Some (Float.to_int f) 198 | Some (J.Int i) -> Some (Int64.to_int i) 199 | _ -> None 200 201(** Decode an operation from JSON (verbose format) *) 202let decode_op (json : J.t) : (Op.op_data, string) result = 203 match json with 204 | J.Object mems -> ( 205 match get_string "op" mems with 206 | None -> Error "missing 'op' field" 207 | Some op_name -> ( 208 match Op.opcode_of_name op_name with 209 | None -> Error (Printf.sprintf "unknown operation: %s" op_name) 210 | Some opcode -> ( 211 match opcode with 212 | Op.New_con -> ( 213 match get_member "value" mems with 214 | Some v -> 215 Ok (Op.Op_new_con { con_value = decode_con_value v }) 216 | None -> Error "new_con: missing 'value' field") 217 | Op.New_val -> Ok Op.Op_new_val 218 | Op.New_obj -> Ok Op.Op_new_obj 219 | Op.New_vec -> Ok Op.Op_new_vec 220 | Op.New_str -> Ok Op.Op_new_str 221 | Op.New_bin -> Ok Op.Op_new_bin 222 | Op.New_arr -> Ok Op.Op_new_arr 223 | Op.Ins_val -> ( 224 match 225 (get_timestamp "obj" mems, get_timestamp "value" mems) 226 with 227 | Some obj, Some value -> 228 Ok 229 (Op.Op_ins_val 230 { ins_val_obj = obj; ins_val_value = value }) 231 | _ -> Error "ins_val: missing 'obj' or 'value' field") 232 | Op.Ins_obj -> ( 233 match (get_timestamp "obj" mems, get_member "value" mems) with 234 | Some obj, Some (J.Array entries) -> 235 let decoded = List.filter_map decode_obj_entry entries in 236 if List.length decoded = List.length entries then 237 Ok 238 (Op.Op_ins_obj 239 { ins_obj_obj = obj; ins_obj_value = decoded }) 240 else Error "ins_obj: invalid entry format" 241 | _ -> Error "ins_obj: missing 'obj' or 'value' field") 242 | Op.Ins_vec -> ( 243 match 244 ( get_timestamp "obj" mems, 245 get_int "idx" mems, 246 get_timestamp "value" mems ) 247 with 248 | Some obj, Some idx, Some value -> 249 Ok 250 (Op.Op_ins_vec 251 { 252 ins_vec_obj = obj; 253 ins_vec_idx = idx; 254 ins_vec_value = value; 255 }) 256 | _ -> Error "ins_vec: missing 'obj', 'idx', or 'value' field" 257 ) 258 | Op.Ins_str -> ( 259 match 260 ( get_timestamp "obj" mems, 261 get_timestamp "after" mems, 262 get_string "value" mems ) 263 with 264 | Some obj, Some after, Some value -> 265 Ok 266 (Op.Op_ins_str 267 { 268 ins_str_obj = obj; 269 ins_str_after = after; 270 ins_str_value = value; 271 }) 272 | _ -> 273 Error "ins_str: missing 'obj', 'after', or 'value' field") 274 | Op.Ins_bin -> ( 275 match 276 ( get_timestamp "obj" mems, 277 get_timestamp "after" mems, 278 get_string "value" mems ) 279 with 280 | Some obj, Some after, Some b64_value -> ( 281 match Base64.decode b64_value with 282 | Ok decoded -> 283 Ok 284 (Op.Op_ins_bin 285 { 286 ins_bin_obj = obj; 287 ins_bin_after = after; 288 ins_bin_value = Bytes.of_string decoded; 289 }) 290 | Error _ -> Error "ins_bin: invalid base64 value") 291 | _ -> 292 Error "ins_bin: missing 'obj', 'after', or 'value' field") 293 | Op.Ins_arr -> ( 294 match 295 ( get_timestamp "obj" mems, 296 get_timestamp "after" mems, 297 get_timestamp "value" mems ) 298 with 299 | Some obj, Some after, Some value -> 300 Ok 301 (Op.Op_ins_arr 302 { 303 ins_arr_obj = obj; 304 ins_arr_after = after; 305 ins_arr_value = value; 306 }) 307 | _ -> 308 Error "ins_arr: missing 'obj', 'after', or 'value' field") 309 | Op.Upd_arr -> ( 310 match 311 ( get_timestamp "obj" mems, 312 get_timestamp "pos" mems, 313 get_timestamp "value" mems ) 314 with 315 | Some obj, Some pos, Some value -> 316 Ok 317 (Op.Op_upd_arr 318 { 319 upd_arr_obj = obj; 320 upd_arr_pos = pos; 321 upd_arr_value = value; 322 }) 323 | _ -> Error "upd_arr: missing 'obj', 'pos', or 'value' field" 324 ) 325 | Op.Del -> ( 326 match (get_timestamp "obj" mems, get_member "what" mems) with 327 | Some obj, Some (J.Array spans) -> 328 let decoded = List.filter_map decode_timespan spans in 329 if List.length decoded = List.length spans then 330 Ok (Op.Op_del { del_obj = obj; del_what = decoded }) 331 else Error "del: invalid timespan format" 332 | _ -> Error "del: missing 'obj' or 'what' field") 333 | Op.Nop -> ( 334 match get_int "len" mems with 335 | Some len -> Ok (Op.Op_nop { nop_len = len }) 336 | None -> 337 (* Default to 1 if no len specified *) 338 Ok (Op.Op_nop { nop_len = 1 }))))) 339 | _ -> Error "expected JSON object" 340 341(** {1 String Encoding/Decoding} *) 342 343(** Encode an operation to a JSON string *) 344let encode (op : Op.op_data) : string = 345 let json = encode_op op in 346 J.to_string json 347 348(** Decode an operation from a JSON string *) 349let decode (s : string) : (Op.op_data, string) result = 350 match Simdjsont.Decode.decode_string Simdjsont.Decode.value s with 351 | Ok json -> decode_op json 352 | Error e -> Error (Printf.sprintf "JSON parse error: %s" e)