a database layer insipred by caqti and ecto
1open Repodb
2
3module YojsonJson : Embedded.JSON with type t = Yojson.Safe.t = struct
4 type t = Yojson.Safe.t
5
6 let null = `Null
7 let to_string json = Yojson.Safe.to_string json
8
9 let of_string s =
10 try Ok (Yojson.Safe.from_string s) with Yojson.Json_error msg -> Error msg
11
12 let get_field json key =
13 match json with `Assoc fields -> List.assoc_opt key fields | _ -> None
14
15 let get_string = function `String s -> Some s | _ -> None
16 let get_int = function `Int i -> Some i | _ -> None
17
18 let get_float = function
19 | `Float f -> Some f
20 | `Int i -> Some (float_of_int i)
21 | _ -> None
22
23 let get_bool = function `Bool b -> Some b | _ -> None
24 let get_list = function `List l -> Some l | _ -> None
25 let is_null = function `Null -> true | _ -> false
26 let make_object fields = `Assoc fields
27 let make_string s = `String s
28 let make_int i = `Int i
29 let make_float f = `Float f
30 let make_bool b = `Bool b
31 let make_list l = `List l
32end
33
34module E = Embedded.Make (YojsonJson)
35
36type address = { street : string; city : string; zip : string }
37
38let address_schema =
39 E.schema ~name:"address"
40 ~decode:(fun json ->
41 match json with
42 | `Assoc fields ->
43 let get_string key =
44 match List.assoc_opt key fields with
45 | Some (`String s) -> s
46 | _ -> ""
47 in
48 Ok
49 {
50 street = get_string "street";
51 city = get_string "city";
52 zip = get_string "zip";
53 }
54 | _ -> Error "Expected object")
55 ~encode:(fun addr ->
56 `Assoc
57 [
58 ("street", `String addr.street);
59 ("city", `String addr.city);
60 ("zip", `String addr.zip);
61 ])
62 ~fields:[ "street"; "city"; "zip" ]
63 ()
64
65let test_from_json () =
66 let json =
67 `Assoc
68 [
69 ("street", `String "123 Main St");
70 ("city", `String "Springfield");
71 ("zip", `String "12345");
72 ]
73 in
74 match E.from_json address_schema json with
75 | Ok embedded ->
76 let data = E.data embedded in
77 Alcotest.(check string) "street" "123 Main St" data.street;
78 Alcotest.(check string) "city" "Springfield" data.city
79 | Error msg -> Alcotest.fail msg
80
81let test_to_json () =
82 let addr = { street = "456 Oak Ave"; city = "Shelbyville"; zip = "67890" } in
83 let embedded =
84 { E.data = addr; fields = [ "street"; "city"; "zip" ]; source = E.Virtual }
85 in
86 let json = E.to_json address_schema embedded in
87 match json with
88 | `Assoc fields -> (
89 match List.assoc_opt "street" fields with
90 | Some (`String s) ->
91 Alcotest.(check string) "street in json" "456 Oak Ave" s
92 | _ -> Alcotest.fail "missing street")
93 | _ -> Alcotest.fail "expected object"
94
95let test_from_json_string () =
96 let json_str = {|{"street":"789 Elm Blvd","city":"Capital","zip":"11111"}|} in
97 match E.from_json_string address_schema json_str with
98 | Ok embedded ->
99 let data = E.data embedded in
100 Alcotest.(check string) "parsed street" "789 Elm Blvd" data.street
101 | Error msg -> Alcotest.fail msg
102
103let test_to_json_string () =
104 let addr = { street = "Test St"; city = "Test City"; zip = "00000" } in
105 let embedded =
106 { E.data = addr; fields = [ "street"; "city"; "zip" ]; source = E.Virtual }
107 in
108 let json_str = E.to_json_string address_schema embedded in
109 Alcotest.(check bool) "contains street" true (String.length json_str > 0)
110
111let test_embeds_one_from_json () =
112 let json =
113 `Assoc
114 [
115 ("street", `String "Single");
116 ("city", `String "Town");
117 ("zip", `String "1");
118 ]
119 in
120 match E.embeds_one_from_json address_schema json with
121 | Ok (Some embedded) ->
122 Alcotest.(check string)
123 "embeds_one street" "Single" (E.data embedded).street
124 | Ok None -> Alcotest.fail "expected Some"
125 | Error msg -> Alcotest.fail msg
126
127let test_embeds_one_from_json_null () =
128 match E.embeds_one_from_json address_schema `Null with
129 | Ok None -> Alcotest.(check pass) "null is None" () ()
130 | Ok (Some _) -> Alcotest.fail "expected None"
131 | Error msg -> Alcotest.fail msg
132
133let test_embeds_many_from_json () =
134 let json =
135 `List
136 [
137 `Assoc
138 [
139 ("street", `String "First");
140 ("city", `String "A");
141 ("zip", `String "1");
142 ];
143 `Assoc
144 [
145 ("street", `String "Second");
146 ("city", `String "B");
147 ("zip", `String "2");
148 ];
149 ]
150 in
151 match E.embeds_many_from_json address_schema json with
152 | Ok embeddeds ->
153 Alcotest.(check int) "two items" 2 (List.length embeddeds);
154 Alcotest.(check string)
155 "first street" "First" (E.data (List.hd embeddeds)).street
156 | Error msg -> Alcotest.fail msg
157
158let tests =
159 [
160 ("from_json", `Quick, test_from_json);
161 ("to_json", `Quick, test_to_json);
162 ("from_json_string", `Quick, test_from_json_string);
163 ("to_json_string", `Quick, test_to_json_string);
164 ("embeds_one from_json", `Quick, test_embeds_one_from_json);
165 ("embeds_one from_json null", `Quick, test_embeds_one_from_json_null);
166 ("embeds_many from_json", `Quick, test_embeds_many_from_json);
167 ]