package ppx_protocol_conv

  1. Overview
  2. Docs

Source file ppx_protocol_driver.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
open Protocol_conv
open Runtime
open StdLabels

module type Parameters = sig
  val field_name: string -> string
  val variant_name: string -> string
  val constructors_without_arguments_as_string: bool
  val omit_default_values: bool
  val eager: bool
  val strict: bool
end

module Default_parameters : Parameters = struct
  let field_name name = name
  let variant_name name = name
  let constructors_without_arguments_as_string = true
  let omit_default_values = true
  let eager = true
  let strict = false
end

module type Driver = sig
  type t
  val to_string_hum: t -> string

  val to_list: t -> t list
  val of_list: t list -> t
  val is_list: t -> bool

  val to_alist: t -> (string * t) list
  val of_alist: (string * t) list -> t
  val is_alist: t -> bool

  val to_char:  t -> char
  val of_char:  char -> t

  val to_int: t -> int
  val of_int: int -> t

  val to_int32: t -> int32
  val of_int32: int32 -> t

  val to_int64: t -> int64
  val of_int64: int64 -> t

  val to_nativeint: t -> nativeint
  val of_nativeint: nativeint -> t

  val to_float: t -> float
  val of_float: float -> t

  val to_string: t -> string
  val of_string: string -> t
  val is_string: t -> bool

  val to_bool: t -> bool
  val of_bool: bool -> t

  val to_bytes: t -> bytes
  val of_bytes: bytes -> t

  val null: t
  val is_null: t -> bool
end

let mangle str =
  let chars =
    let chars = ref [] in
    String.iter ~f:(fun ch -> chars := ch :: !chars) str;
    List.rev !chars
  in
  let rec inner = function
    | '_' :: '_' :: cs -> inner ('_' :: cs)
    | '_' :: c :: cs -> Char.uppercase_ascii c :: inner cs
    | '_' :: [] -> []
    | c :: cs -> c :: inner cs
    | [] -> []
  in
  let res_arr = inner chars |> Array.of_list in
  String.init (Array.length res_arr) ~f:(fun i -> res_arr.(i))

module Make(Driver: Driver)(P: Parameters) = struct
  type t = Driver.t
  type error = string * t option
  exception Protocol_error of error

  let make_error ?value msg = (msg, value)

  let error_to_string_hum: error -> string = function
    | (s, Some t) -> Printf.sprintf "%s. Got: %s" s (Driver.to_string_hum t)
    | (s, None) -> s

  (* Register exception printer *)
  let () = Printexc.register_printer (function
      | Protocol_error err -> Some (error_to_string_hum err)
      | _ -> None)

  let to_string_hum = Driver.to_string_hum

  let raise_errorf t fmt =
    Printf.kprintf (fun s -> raise (Protocol_error (s, t))) fmt

  let try_with: (t -> 'a) -> t -> ('a, error) Runtime.result = fun f t ->
    match f t with
    | v -> Ok v
    | exception (Protocol_error e) -> Error e

  let wrap t f x = match f x with
    | v -> v
    | exception Helper.Protocol_error s -> raise (Protocol_error (s, Some t))
    | exception e -> raise (Protocol_error (Printexc.to_string e, Some t))


  let to_record: (t, 'a, 'b) Record_in.t -> 'a -> t -> 'b = fun spec constr ->
    let spec = Helper.map_record_in P.field_name spec in
    let f = Helper.to_record ~strict:P.strict spec constr in
    fun t -> wrap t f (wrap t Driver.to_alist t)

  let of_record: type a. (t, a, t) Record_out.t -> a = fun spec ->
    let spec = Helper.map_record_out P.field_name spec in
    Helper.of_record ~omit_default:P.omit_default_values Driver.of_alist spec

  let to_tuple: (t, 'a, 'b) Tuple_in.t -> 'a -> t -> 'b = fun spec constr ->
    let f = Helper.to_tuple spec constr in
    fun t -> wrap t f (wrap t Driver.to_list t)

  let of_tuple: (t, 'a, t) Tuple_out.t -> 'a = fun spec ->
    Helper.of_tuple Driver.of_list spec

  let to_variant: (t, 'a) Variant_in.t list -> t -> 'a = fun spec ->
    let f = Helper.to_variant (Helper.map_constructor_names P.variant_name spec) in

    match P.constructors_without_arguments_as_string with
    | true -> begin
        function
        | t when Driver.is_string t -> wrap t (f (wrap t Driver.to_string t)) []
        | t when Driver.is_list t -> begin
            match Driver.to_list t with
            | name :: args when Driver.is_string name -> wrap t f ((Driver.to_string name)) args
            | _ :: _ -> raise_errorf (Some t) "First element in the list must be the constructor name when name when deserialising variant"
            | [] -> raise_errorf (Some t) "Empty list found when deserialising variant"
          end
        | t -> raise_errorf (Some t) "Expected list or string when deserialising variant"
      end
    | false -> begin
        function
        | t when Driver.is_list t -> begin
            match Driver.to_list t with
            | name :: args when Driver.is_string name -> wrap t (f (Driver.to_string name)) args
            | _ :: _ -> raise_errorf (Some t) "First element in the list must be the constructor name when name when deserialising variant"
            | [] -> raise_errorf (Some t) "Empty list found when deserialising variant"
          end
        | t -> raise_errorf (Some t) "Expected list when deserialising variant"
      end

  let of_variant: string -> (t, 'a, t) Tuple_out.t -> 'a =
    let of_variant name =
      let name = P.variant_name name |> Driver.of_string in
      function
      | [] when P.constructors_without_arguments_as_string -> name
      | ts -> Driver.of_list (name :: ts)
    in
    fun name spec -> Helper.of_variant of_variant name spec

  let get_option = function
    | t when Driver.is_alist t -> begin
        match Driver.to_alist t with
        | [("__option", t)] -> Some t
        | _ -> None
      end
    | _ -> None

  (* If the type is an empty list, thats also null. *)
  let to_option: (t -> 'a) -> t -> 'a option = fun to_value_fun -> function
    | t when Driver.is_null t -> None
    | t ->
      let t = match (get_option t) with Some t -> t | None -> t in
      Some (to_value_fun t)

  let of_option: ('a -> t) -> 'a option -> t = fun of_value_fun -> function
    | None -> Driver.null
    | Some v ->
      let mk_option t = Driver.of_alist [ ("__option", t) ] in
      match of_value_fun v with
      | t when Driver.is_null t -> mk_option t
      | t when (get_option t) <> None ->
        mk_option t
      | t -> t

  let to_ref: (t -> 'a) -> t -> 'a ref = fun to_value_fun t ->
      let v = to_value_fun t in
      ref v

  let of_ref: ('a -> t) -> 'a ref -> t = fun of_value_fun v ->
    of_value_fun !v

  let to_result: (t -> 'a) -> (t -> 'b) -> t -> ('a, 'b) result = fun to_ok to_err ->
    let ok = Runtime.Tuple_in.(Cons (to_ok, Nil)) in
    let err = Runtime.Tuple_in.(Cons (to_err, Nil)) in
    to_variant Runtime.Variant_in.[Variant ("Ok", ok, fun v -> Ok v); Variant ("Error", err, fun v -> Error v)]

  let of_result: ('a -> t) -> ('b -> t) -> ('a, 'b) result -> t = fun of_ok of_err ->
    let of_ok = of_variant "Ok" Runtime.Tuple_out.(Cons (of_ok, Nil)) in
    let of_err = of_variant "Error" Runtime.Tuple_out.(Cons (of_err, Nil)) in
    function
    | Ok ok -> of_ok ok
    | Error err -> of_err err

  let to_list: (t -> 'a) -> t -> 'a list = fun to_value_fun t ->
    Helper.list_map ~f:to_value_fun (wrap t Driver.to_list t)

  let of_list: ('a -> t) -> 'a list -> t = fun of_value_fun v ->
    Helper.list_map ~f:of_value_fun v |> Driver.of_list

  let to_array: (t -> 'a) -> t -> 'a array = fun to_value_fun t ->
    to_list to_value_fun t |> Array.of_list

  let of_array: ('a -> t) -> 'a array -> t = fun of_value_fun v ->
    Array.to_list v |> of_list of_value_fun

  let to_lazy_t: (t -> 'a) -> t -> 'a lazy_t = fun to_value_fun ->
    match P.eager with
    | true -> fun t -> Lazy.from_val (to_value_fun t)
    | false -> fun t -> Lazy.from_fun (fun () -> to_value_fun t)

  let of_lazy_t: ('a -> t) -> 'a lazy_t -> t = fun of_value_fun v ->
    Lazy.force v |> of_value_fun

  let to_char t = try Driver.to_char t with _ -> raise_errorf (Some t) "char expected"
  let of_char = Driver.of_char

  let to_int t = try Driver.to_int t with _ -> raise_errorf (Some t) "int expected"
  let of_int = Driver.of_int

  let to_int32 t = try Driver.to_int32 t with _ -> raise_errorf (Some t) "int32 expected"
  let of_int32 = Driver.of_int32

  let to_int64 t = try Driver.to_int64 t with _ -> raise_errorf (Some t) "int64 expected"
  let of_int64 = Driver.of_int64

  let to_nativeint t = try Driver.to_nativeint t with _ -> raise_errorf (Some t) "nativeint expected"
  let of_nativeint = Driver.of_nativeint

  let to_string t = try Driver.to_string t with _ -> raise_errorf (Some t) "string expected"
  let of_string = Driver.of_string

  let to_float t = try Driver.to_float t with _ -> raise_errorf (Some t) "float expected"
  let of_float = Driver.of_float

  let to_bool t = try Driver.to_bool t with _ -> raise_errorf (Some t) "bool expected"
  let of_bool = Driver.of_bool

  let to_bytes t = try Driver.to_bytes t with _ -> raise_errorf (Some t) "bytes expected"
  let of_bytes = Driver.of_bytes

  let to_unit t = to_option (fun _ -> ()) t
                  |> function Some _ -> raise_errorf (Some t) "Unit expected"
                            | None -> ()

  let of_unit () = of_option (fun _ -> failwith "Should call with None") None
end
OCaml

Innovation. Community. Security.