objective categorical abstract machine language personal data server

DAG-CBOR to/of yojson

futur.blue 116aafe3 04c19333

verified
+109 -6
+2
dune-project
··· 43 43 (depends 44 44 ocaml 45 45 dune 46 + (base64 (>= 3.5.1)) 46 47 (digestif (>= 1.2.0)) 47 48 (multibase (>= 0.1.0)) 49 + (yojson (>= 3.0.0)) 48 50 (alcotest :with-test)))
+2
ipld.opam
··· 10 10 depends: [ 11 11 "ocaml" 12 12 "dune" {>= "3.14"} 13 + "base64" {>= "3.5.1"} 13 14 "digestif" {>= "1.2.0"} 14 15 "multibase" {>= "0.1.0"} 16 + "yojson" {>= "3.0.0"} 15 17 "alcotest" {with-test} 16 18 "odoc" {with-doc} 17 19 ]
+58
ipld/lib/dag_cbor.ml
··· 27 27 | `Map of value StringMap.t 28 28 | `Link of Cid.t ] 29 29 30 + let rec of_yojson (json : Yojson.Safe.t) : value = 31 + match json with 32 + | `Assoc [("$bytes", `String s)] -> 33 + `Bytes (Bytes.of_string (Base64.decode_exn s)) 34 + | `Assoc [("$link", `String s)] -> 35 + `Link (Result.get_ok (Cid.of_string s)) 36 + | `Assoc assoc_list -> 37 + `Map 38 + (StringMap.of_list 39 + (List.map (fun (k, v) -> (k, of_yojson v)) assoc_list) ) 40 + | `List lst -> 41 + `Array (Array.of_list (List.map of_yojson lst)) 42 + | `Bool b -> 43 + `Boolean b 44 + | `Int i -> 45 + `Integer (Int64.of_int i) 46 + | `Intlit s -> 47 + `Integer (Int64.of_string s) 48 + | `Float f -> 49 + `Float f 50 + | `String s -> 51 + `String s 52 + | `Null -> 53 + `Null 54 + 55 + let rec to_yojson (value : value) : Yojson.Safe.t = 56 + match value with 57 + | `Map map -> 58 + `Assoc (StringMap.to_list map |> List.map (fun (k, v) -> (k, to_yojson v))) 59 + | `Array arr -> 60 + `List (Array.to_list arr |> List.map to_yojson) 61 + | `Bytes bytes -> 62 + `Assoc 63 + [ ( "$bytes" 64 + , `String (Base64.encode_exn ~pad:false (Bytes.to_string bytes)) ) ] 65 + | `Link cid -> 66 + `Assoc [("$link", `String (Cid.to_string cid))] 67 + | `Boolean b -> 68 + `Bool b 69 + | `Integer i -> 70 + `Intlit (Int64.to_string i) 71 + | `Float f -> 72 + `Float f 73 + | `String s -> 74 + `String s 75 + | `Null -> 76 + `Null 77 + 30 78 module Encoder = struct 31 79 type t = {mutable buf: Buffer.t; mutable pos: int} 32 80 ··· 159 207 if encoder.pos < Buffer.length encoder.buf then 160 208 Buffer.truncate encoder.buf encoder.pos ; 161 209 Buffer.to_bytes encoder.buf 210 + 211 + let encode_yojson (v : Yojson.Safe.t) : bytes = of_yojson v |> encode 162 212 end 163 213 164 214 module Decoder = struct ··· 357 407 (Printf.sprintf "decode: extra bytes after valid CBOR data (%d)" 358 408 (Bytes.length remainder) ) ; 359 409 value 410 + 411 + let decode_to_yojson buf = 412 + let value = decode buf in 413 + to_yojson value 360 414 end 361 415 362 416 let encode = Encoder.encode 363 417 364 418 let decode = Decoder.decode 419 + 420 + let encode_yojson = Encoder.encode_yojson 421 + 422 + let decode_to_yojson = Decoder.decode_to_yojson
+1 -1
ipld/lib/dune
··· 1 1 (library 2 2 (name ipld) 3 3 (wrapped false) 4 - (libraries digestif multibase)) 4 + (libraries base64 digestif multibase yojson))
+1 -1
ipld/test/dune
··· 1 1 (tests 2 2 (names test_cid test_dag_cbor) 3 3 (package ipld) 4 - (libraries ipld alcotest)) 4 + (libraries ipld str alcotest))
+45 -4
ipld/test/test_dag_cbor.ml
··· 3 3 let rec stringify_map m = 4 4 StringMap.bindings m 5 5 |> List.map (fun (k, v) -> 6 - Format.sprintf "%s: %s" k (stringify_ipld_value v) ) 7 - |> String.concat ", " |> Format.sprintf "{ %s }" 6 + Format.sprintf "\"%s\": %s" k (stringify_ipld_value v) ) 7 + |> String.concat ", " |> Format.sprintf "{%s}" 8 8 9 9 and stringify_ipld_value (value : Dag_cbor.value) = 10 10 match value with ··· 15 15 | `Float f -> 16 16 Format.sprintf "%f" f 17 17 | `String s -> 18 - Format.sprintf "\"%s\"" s 18 + Format.sprintf "\"%s\"" 19 + (Str.global_replace (Str.regexp_string {|"|}) {|\"|} s) 19 20 | `Bytes b -> 20 21 Format.sprintf "%s" (Bytes.to_string b) 21 22 | `Map m -> ··· 58 59 false 59 60 60 61 let ipld_testable = Alcotest.testable pprint_ipld_value ipld_value_eq 62 + 63 + let yojson_testable = Alcotest.testable Yojson.Safe.pp Yojson.Safe.equal 61 64 62 65 let to_base_16 bytes = 63 66 let hex_of_nibble n = ··· 383 386 Alcotest.(check bool) "second object decoded correctly" true (decoded2 = obj2) ; 384 387 Alcotest.(check int) "no remaining bytes" 0 (Bytes.length final_remainder) 385 388 389 + let test_yojson_roundtrip () = 390 + let record_embed_images_0_aspect_ratio : Yojson.Safe.t = 391 + `Assoc [("height", `Intlit "885"); ("width", `Intlit "665")] 392 + in 393 + let record_embed_images_0_image : Yojson.Safe.t = 394 + `Assoc 395 + [ ("height", `Intlit "885") 396 + ; ("width", `Intlit "665") 397 + ; ("mimeType", `String "image/jpeg") 398 + ; ("size", `Intlit "645553") ] 399 + in 400 + let record_embed_images_0 : Yojson.Safe.t = 401 + `Assoc 402 + [ ( "alt" 403 + , `String 404 + "a photoshopped picture of kit with a microphone. kit is saying \ 405 + \"meow\"" ) 406 + ; ("aspectRatio", record_embed_images_0_aspect_ratio) 407 + ; ("image", record_embed_images_0_image) ] 408 + in 409 + let record_embed : Yojson.Safe.t = 410 + `Assoc 411 + [ ("$type", `String "app.bsky.embed.images") 412 + ; ("images", `List [record_embed_images_0]) ] 413 + in 414 + let record : Yojson.Safe.t = 415 + `Assoc 416 + [ ("$type", `String "app.bsky.feed.post") 417 + ; ("createdAt", `String "2024-08-13T01:16:06.453Z") 418 + ; ("langs", `List [`String "en"]) 419 + ; ("text", `String "exclusively on bluesky") 420 + ; ("embed", record_embed) ] 421 + in 422 + let encoded = Dag_cbor.encode_yojson record in 423 + let decoded = Dag_cbor.decode_to_yojson encoded in 424 + Alcotest.(check yojson_testable) "yojson roundtrip" record decoded 425 + 386 426 let () = 387 427 Alcotest.run "dag-cbor" 388 428 [ ( "dag-cbor encoding" ··· 390 430 ; ("round_trip", `Quick, test_round_trip) 391 431 ; ("atproto_records", `Quick, test_atproto_post_records) 392 432 ; ("invalid_numbers", `Quick, test_invalid_numbers) 393 - ; ("decode_multiple", `Quick, test_decode_multiple_objects) ] ) ] 433 + ; ("decode_multiple", `Quick, test_decode_multiple_objects) 434 + ; ("yojson_roundtrip", `Quick, test_yojson_roundtrip) ] ) ]