Source file router.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
module Dream = Dream__pure.Inmost
type token =
| Literal of string
| Param of string
| Wildcard of string
let rec validate route = function
| (Param "")::_ ->
Printf.ksprintf failwith "Empty path parameter name in '%s'" route
| [Wildcard "*"] ->
()
| (Wildcard "*")::_ ->
failwith "Path wildcard must be last"
| (Wildcard _)::_ ->
failwith "Path wildcard must be just '**'"
| _::tokens ->
validate route tokens
| [] ->
()
let make_star_or_wildcard = function
| "" -> Literal "*"
| s -> Wildcard s
let parse string =
let rec parse_separator tokens index =
match string.[index] with
| '/' ->
parse_component_start tokens (index + 1)
| _ ->
parse_component_start tokens index
| exception Invalid_argument _ ->
List.rev tokens
and parse_component_start tokens index =
match string.[index] with
| '/' ->
parse_component_start tokens (index + 1)
| ':' ->
parse_component tokens (fun s -> Param s) (index + 1) (index + 1)
| '*' ->
parse_component tokens make_star_or_wildcard (index + 1) (index + 1)
| _ | exception Invalid_argument _ ->
parse_component tokens (fun s -> Literal s) index index
and parse_component tokens constructor start_index index =
match string.[index] with
| exception Invalid_argument _ ->
let token =
constructor (String.sub string start_index (index - start_index)) in
List.rev (token::tokens)
| '/' ->
let token =
constructor (String.sub string start_index (index - start_index)) in
parse_separator (token::tokens) index
| _ ->
parse_component tokens constructor start_index (index + 1)
in
let tokens = parse_separator [] 0 in
validate string tokens;
tokens
let rec strip_empty_trailing_token = function
| [] -> []
| [Literal ""] -> []
| token::tokens -> token::(strip_empty_trailing_token tokens)
type method_set = [
| Dream.method_
| `Any
]
let method_matches method_set method_ =
match method_set with
| #Dream.method_ as method' -> Dream.methods_equal method' method_
| `Any -> true
type node =
| Handler of method_set * Dream.handler
| Scope of route
and route = (token list * node) list
let get pattern handler =
[parse pattern, Handler (`GET, handler)]
let post pattern handler =
[parse pattern, Handler (`POST, handler)]
let put pattern handler =
[parse pattern, Handler (`PUT, handler)]
let delete pattern handler =
[parse pattern, Handler (`DELETE, handler)]
let head pattern handler =
[parse pattern, Handler (`HEAD, handler)]
let connect pattern handler =
[parse pattern, Handler (`CONNECT, handler)]
let options pattern handler =
[parse pattern, Handler (`OPTIONS, handler)]
let trace pattern handler =
[parse pattern, Handler (`TRACE, handler)]
let patch pattern handler =
[parse pattern, Handler (`PATCH, handler)]
let any pattern handler =
[parse pattern, Handler (`Any, handler)]
let no_route =
[]
let rec apply middlewares routes =
let rec compose handler = function
| [] -> handler
| middleware::more -> middleware @@ compose handler more
in
routes
|> List.flatten
|> List.map (fun (pattern, node) ->
let node =
match node with
| Handler (method_, handler) ->
Handler (method_, compose handler middlewares)
| Scope route -> Scope (apply middlewares [route])
in
pattern, node)
let under prefix routes =
[strip_empty_trailing_token (parse prefix), Scope (List.flatten routes)]
let scope prefix middlewares routes =
under prefix [apply middlewares routes]
let params : (string * string) list Dream.local =
Dream.new_local ()
let log =
Log.sub_log "dream.router"
let missing_param name request =
let message = Printf.sprintf "Dream.param: missing path parameter %S" name in
log.error (fun log -> log ~request "%s" message);
failwith message
let param name request =
match Dream.local params request with
| None -> missing_param name request
| Some params ->
try List.assoc name params
with _ -> missing_param name request
let router routes =
let routes = List.flatten routes in
fun next_handler request ->
let rec try_routes bindings prefix path routes ok fail =
match routes with
| [] -> fail ()
| (pattern, node)::routes ->
try_route bindings prefix path pattern node ok (fun () ->
try_routes bindings prefix path routes ok fail)
and try_route bindings prefix path pattern node ok fail =
match pattern, path with
| [], _ ->
try_node bindings prefix path node false ok fail
| _, [] -> fail ()
| Literal s :: pattern, s' :: path when s = s' ->
try_route bindings (s'::prefix) path pattern node ok fail
| Literal _ :: _, _ -> fail ()
| Param _ :: _, s' :: _ when s' = "" -> fail ()
| Param s :: pattern, s' :: path ->
try_route ((s, s')::bindings) (s'::prefix) path pattern node ok fail
| Wildcard _ :: _, _ ->
try_node bindings prefix path node true ok fail
and try_node bindings prefix path node is_wildcard ok fail =
match node with
| Handler (method_, handler)
when method_matches method_ (Dream.method_ request) ->
let request = Dream.with_local params bindings request in
if is_wildcard then
request
|> Dream.with_prefix prefix
|> Dream.with_path path
|> ok handler
else
if path = [] then
ok handler request
else
fail ()
| Handler _ -> fail ()
| Scope routes -> try_routes bindings prefix path routes ok fail
in
let params =
match Dream.local params request with
| Some params -> params
| None -> []
in
let prefix = Dream.internal_prefix request in
let path = Dream.path request in
try_routes
params prefix path routes
(fun handler request -> handler request)
(fun () -> next_handler request)