Source file conformist.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
type date = int * int * int
type 'a decoder = string -> ('a, string) result
type 'a validator = 'a -> string option
let always_valid _ = None
module Field = struct
type ('meta, 'a) t = {
name : string;
meta : 'meta option;
decoder : 'a decoder;
validator : 'a validator;
optional : bool;
}
type (_, _, _) list =
| [] : ('meta, 'ty, 'ty) list
| ( :: ) :
('meta, 'a) t * ('meta, 'b, 'ty) list
-> ('meta, 'a -> 'b, 'ty) list
type _ any_field = AnyField : ('meta, 'a) t -> 'meta any_field
let meta (AnyField field) = field.meta
let name (AnyField field) = field.name
let validate (AnyField field) input =
match field.decoder input with
| Ok value -> field.validator value
| Error msg -> Some msg
let optional (AnyField field) = field.optional
let make name meta decoder validator optional =
{ name; meta; decoder; validator; optional }
let make_custom name decoder ?meta ?(validator = always_valid) () =
let decoder string = decoder string in
make name meta decoder validator false
let make_optional ?meta field =
let decoder string =
match string with
| "" -> Ok None
| string -> (
match field.decoder string with
| Ok result -> Ok (Some result)
| Error msg -> Error msg )
in
let validator a =
match a with Some a -> field.validator a | None -> None
in
make field.name meta decoder validator true
let make_bool name ?meta ?(msg = "Invalid value provided") () =
let decoder string = try Ok (bool_of_string string) with _ -> Error msg in
make name meta decoder always_valid false
let make_float name ?meta ?(msg = "Invalid number provided")
?(validator = always_valid) () =
let decoder string =
try Ok (float_of_string string) with _ -> Error msg
in
make name meta decoder validator false
let make_int name ?meta ?(msg = "Invalid number provided")
?(validator = always_valid) () =
let decoder string = try Ok (int_of_string string) with _ -> Error msg in
make name meta decoder validator false
let make_string name ?meta ?(validator = always_valid) () =
let decoder string = Ok string in
make name meta decoder validator false
let make_date name ?meta ?(msg = "Invalid date provided")
?(validator = always_valid) () =
let decoder string =
match String.split_on_char '-' string with
| [ y; m; d ] -> (
match
(int_of_string_opt y, int_of_string_opt m, int_of_string_opt d)
with
| Some y, Some m, Some d -> Ok (y, m, d)
| _ -> Error msg )
| _ -> Error msg
in
make name meta decoder validator false
end
let custom = Field.make_custom
let optional = Field.make_optional
let bool = Field.make_bool
let float = Field.make_float
let int = Field.make_int
let string = Field.make_string
let date = Field.make_date
type ('meta, 'ctor, 'ty) t = {
fields : ('meta, 'ctor, 'ty) Field.list;
ctor : 'ctor;
}
let empty = { fields = Field.[]; ctor = () }
let make fields ctor = { fields; ctor }
let rec fold_left' :
type ty args.
f:('res -> 'meta Field.any_field -> 'res) ->
init:'res ->
('meta, args, ty) Field.list ->
'res =
fun ~f ~init fields ->
match fields with
| [] -> init
| field :: fields -> fold_left' ~f ~init:(f init (AnyField field)) fields
let fold_left ~f ~init schema = fold_left' ~f ~init schema.fields
type validation_error = (string * string) list
type input = (string * string list) list
let validate schema input =
let f errors field =
let name = Field.name field in
match List.assoc name input with
| [ value_string ] -> (
match Field.validate field value_string with
| Some msg -> List.cons (name, msg) errors
| None -> errors )
| _ -> List.cons (name, "Multiple values provided") errors
| exception Not_found ->
if Field.optional field then errors
else List.cons (name, "No value provided") errors
in
fold_left ~f ~init:[] schema |> List.rev
let rec decode :
type meta ctor ty.
(meta, ctor, ty) t -> (string * string list) list -> (ty, string) Result.t =
fun { fields; ctor } fields_assoc ->
let open! Field in
match fields with
| [] -> Ok ctor
| field :: fields -> (
match List.assoc field.name fields_assoc with
| [ value_string ] -> (
match field.decoder value_string with
| Ok value -> (
match ctor value with
| ctor -> decode { fields; ctor } fields_assoc
| exception _ ->
Error
(Printf.sprintf "Failed to decode value '%s' of field '%s'"
value_string field.name) )
| Error msg ->
Error
(Printf.sprintf "Failed to decode value '%s' of field '%s': %s"
field.name value_string msg) )
| [] -> (
match field.decoder "" with
| Ok value -> (
match ctor value with
| ctor -> decode { fields; ctor } fields_assoc
| exception _ ->
Error
(Printf.sprintf "Failed to decode value '%s' of field '%s'"
"" field.name) )
| Error msg ->
Error
(Printf.sprintf "Failed to decode value '%s' of field '%s': %s"
field.name "" msg) )
| _ ->
Error
(Printf.sprintf
"Failed to decode field '%s': Multiple values provided"
field.name)
| exception Not_found ->
Error
(Printf.sprintf "Failed to decode field '%s': No value provided"
field.name) )