package decoders

  1. Overview
  2. Docs

Source file sig.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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
open Util

type ('good, 'bad) result = ('good, 'bad) My_result.t =
  | Ok of 'good
  | Error of 'bad

(** User-facing Decoder interface. *)
module type S = sig
  (** The type of values to be decoded (e.g. JSON or Yaml). *)
  type value

  type error = value Error.t

  val pp_error : Format.formatter -> error -> unit

  val string_of_error : error -> string

  val of_string : string -> (value, error) result

  val of_file : string -> (value, error) result

  (** The type of decoders.

      Use the functions below to construct decoders for your data types.

      To run a decoder, pass it to {!val:decode_value}.
  *)
  type 'a decoder = (value, 'a) Decoder.t

  (** {2 Primitives} *)

  val string : string decoder
  (** Decode a [string]. *)

  val int : int decoder
  (** Decode an [int]. *)

  val float : float decoder
  (** Decode a [float]. *)

  val bool : bool decoder
  (** Decode a [bool]. *)

  val null : unit decoder
  (** Decode a [null]. *)

  val value : value decoder
  (** Decode a literal [value]. *)

  (** {2 Lists} *)

  val list : 'a decoder -> 'a list decoder
  (** Decode a collection into an OCaml list. *)

  val list_filter : 'a option decoder -> 'a list decoder
  (** Decode a collection into an OCaml list, skipping elements for which the
      decoder returns None.
  *)

  val list_fold_left : ('a -> 'a decoder) -> 'a -> 'a decoder
  (** Decode a collection with an accumulator.

      If we consider that an ['a decoder] is basically a type alias for
      [json -> ('a, error) result], the signature of this function is comparable
      to that of [List.fold_left]:

      {[
      val List.fold_left : ('a ->   'b ->                 'a) -> 'a -> 'b list ->                 'a
      val list_fold_left : ('a -> json -> ('a, error) result) -> 'a ->    json -> ('a, error) result
      val list_fold_left : ('a ->                 'a decoder) -> 'a ->                    'a decoder
      ]}
  *)

  val array : 'a decoder -> 'a array decoder
  (** Decode a collection into an OCaml array. *)

  val index : int -> 'a decoder -> 'a decoder
  (** Decode a collection, requiring a particular index. *)

  val uncons : ('a -> 'b decoder) -> 'a decoder -> 'b decoder
  (** [fst |> uncons rest] decodes the first element of a list using [fst], then
      decodes the remainder of the list using [rest].

      For example, to decode this s-expression:

      {[
          (library
            (name decoders))
      ]}

      we can use this decoder:

      {[
          string |> uncons (function
            | "library" -> field "name" string
            | _ -> fail "Expected a library stanza")
      ]}

      As another example, say you have a JSON array that starts with a string,
      then a bool, then a list of integers:

      {[
          ["hello", true, 1, 2, 3, 4]
      ]}

      We could decode it like this:

      {[
          let (>>=::) fst rest = uncons rest fst

          let decoder : (string * bool * int list) decoder =
            string >>=:: fun the_string ->
            bool >>=:: fun the_bool ->
            list int >>= fun the_ints ->
            succeed (the_string, the_bool, the_ints)
      ]}

      (If you squint, the uncons operator [>>=::] kind of looks like the cons
      operator [::].)
  *)

  (** {1 Object primitives} *)

  val field : string -> 'a decoder -> 'a decoder
  (** Decode an object, requiring a particular field. *)

  val field_opt : string -> 'a decoder -> 'a option decoder
  (** Decode an object, where a particular field may or may not be present.

      For example, [(field_opt "hello" int)]:

      - when run on [{"hello": 123}], will succeed with [Some 123]
      - when run on [{"hello": null}], will fail
      - when run on [{"world": 123}], will succeed with [None]
      - when run on [["a", "list", "of", "strings"]], will fail
  *)

  val field_opt_or : default:'a -> string -> 'a decoder -> 'a decoder
  (** Similar to {!field_opt} but with a default value.
      @since 0.7 *)

  val single_field : (string -> 'a decoder) -> 'a decoder
  (** Decode an object, requiring exactly one field. *)

  val at : string list -> 'a decoder -> 'a decoder
  (** Decode a nested object, requiring certain fields. *)

  (** {2 Inconsistent structure} *)

  val maybe : 'a decoder -> 'a option decoder
  (** [maybe d] is a decoder that always succeeds. If [d] succeeds with [x],
      then [maybe d] succeeds with [Some x], otherwise if [d] fails, then [maybe d]
      succeeds with [None].

      For example, [maybe (field "hello" int)]:

      - when run on [{"hello": 123}], will succeed with [Some 123]
      - when run on [{"hello": null}], will succeed with [None]
      - when run on [{"world": 123}], will succeed with [None]
      - when run on [["a", "list", "of", "strings"]], will succeed with [None]

  *)

  val nullable : 'a decoder -> 'a option decoder
  (** [nullable d] will succeed with [None] if the JSON value is [null]. If the
      JSON value is non-[null], it wraps the result of running [x] in a [Some].

      For example, [field "hello" (nullable int)]:

      - when run on [{"hello": 123}], will succeed with [Some 123]
      - when run on [{"hello": null}], will succeed with [None]
      - when run on [{"world": 123}], will fail
      - when run on [["a", "list", "of", "strings"]], will fail

  *)

  val one_of : (string * 'a decoder) list -> 'a decoder
  (** Try a sequence of different decoders. *)

  val pick : (string * 'a decoder decoder) list -> 'a decoder
  (** [pick choices] picks a single choice, like {!one_of}.
      However, each element of [choices] can look at the value, decide if
      it applies (e.g. based on the value of a single field, like a "kind"
      or "type" field), and if it does, returns a decoder for the rest of
      the value.

      If a choice is made, even if the returned sub-decoder fails, the
      error message will totally ignore the rest of the choices and only be
      about the choice that was initially made.

      @since 0.7 *)

  val decode_sub : value -> 'a decoder -> 'a decoder
  (** [decode_sub value sub_dec] uses [sub_dec] to decode [value].
      This is useful when one has a value on hand.
      @since 0.7 *)

  (** {2 Mapping} *)

  val map : ('a -> 'b) -> 'a decoder -> 'b decoder
  (** Map over the result of a decoder. *)

  val apply : ('a -> 'b) decoder -> 'a decoder -> 'b decoder
  (** Try two decoders and then combine the result. We can use this to decode
      objects with many fields (but it's preferable to use [Infix.(>>=)] - see the README).
  *)

  (** {2 Working with object keys} *)

  val keys : string list decoder
  (** Decode all of the keys of an object to a list of strings. *)

  val key_value_pairs : 'v decoder -> (string * 'v) list decoder
  (** Decode an object into a list of key-value pairs. *)

  val key_value_pairs_seq : (string -> 'v decoder) -> 'v list decoder
  (** Decode an object into a list of values, where the value
      decoder depends on the key. *)

  val keys' : 'k decoder -> 'k list decoder
  (** [keys'] is for when your keys might not be strings - probably only likely for Yaml. *)

  val key_value_pairs' : 'k decoder -> 'v decoder -> ('k * 'v) list decoder

  val key_value_pairs_seq' : 'k decoder -> ('k -> 'v decoder) -> 'v list decoder

  (** {2 Fancy decoding} *)

  val succeed : 'a -> 'a decoder
  (** A decoder that always succeeds with the argument, ignoring the input. *)

  val fail : string -> 'a decoder
  (** A decoder that always fails with the given message, ignoring the input. *)

  val fail_with : error -> 'a decoder

  val from_result : ('a, error) result -> 'a decoder

  val and_then : ('a -> 'b decoder) -> 'a decoder -> 'b decoder
  (** Create decoders that depend on previous results. *)

  val fix : ('a decoder -> 'a decoder) -> 'a decoder
  (** Recursive decoders.

      [let my_decoder = fix (fun my_decoder -> ...)] allows you to define
      [my_decoder] in terms of itself.
  *)

  val of_of_string : msg:string -> (string -> 'a option) -> 'a decoder
  (** Create a decoder from a function [of_string : string -> 'a option] *)

  module Infix : sig
    include module type of Decoder.Infix

    val ( <$> ) : ('a -> 'b) -> 'a decoder -> 'b decoder
  end

  include module type of Infix

  (** {2 Running decoders} *)

  val decode_value : 'a decoder -> value -> ('a, error) result
  (** Run a decoder on some input. *)

  val decode_string : 'a decoder -> string -> ('a, error) result
  (** Run a decoder on a string. *)

  val decode_file : 'a decoder -> string -> ('a, error) result
  (** Run a decoder on a file. *)

  (** {2 Pipeline Decoders} *)
  module Pipeline : sig
    (**
        Pipeline decoders present an alternative to the [mapN] style. They read
        more naturally, but can lead to harder-to-understand type errors.
      {[
        let person_decoder : person decoder =
          decode as_person
          |> required "name" string
          |> required "age" int
      ]}
    *)

    val decode : 'a -> 'a decoder

    val required : string -> 'a decoder -> ('a -> 'b) decoder -> 'b decoder

    val required_at :
      string list -> 'a decoder -> ('a -> 'b) decoder -> 'b decoder

    val optional :
      string -> 'a decoder -> 'a -> ('a -> 'b) decoder -> 'b decoder

    val optional_at :
      string list -> 'a decoder -> 'a -> ('a -> 'b) decoder -> 'b decoder

    val custom : 'a decoder -> ('a -> 'b) decoder -> 'b decoder
  end
end

(** Signature of things that can be decoded. *)
module type Decodeable = sig
  type value

  val pp : Format.formatter -> value -> unit

  val of_string : string -> (value, string) result

  val of_file : string -> (value, string) result

  val get_string : value -> string option

  val get_int : value -> int option

  val get_float : value -> float option

  val get_bool : value -> bool option

  val get_null : value -> unit option

  val get_list : value -> value list option

  val get_key_value_pairs : value -> (value * value) list option

  val to_list : value list -> value
end