Skip to content

Commit 3d90072

Browse files
committed
separate full splat handling
Signed-off-by: Rudi Grinberg <[email protected]>
1 parent 9a5787d commit 3d90072

File tree

4 files changed

+134
-62
lines changed

4 files changed

+134
-62
lines changed

opium/src/router.ml

Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,42 @@ module Params = struct
8585
type t =
8686
{ named : (string * string) list
8787
; unnamed : string list
88+
; full_splat : string option
8889
}
8990

90-
let make ~named ~unnamed = { named; unnamed }
91+
let make ~named ~unnamed ~full_splat = { named; unnamed; full_splat }
9192
let all_named t = t.named
9293

93-
let sexp_of_t { named; unnamed } =
94+
let sexp_of_t { named; unnamed; full_splat } =
9495
let open Sexp_conv in
9596
Sexp.List
9697
[ List
9798
[ Atom "named"
9899
; sexp_of_list (sexp_of_pair sexp_of_string sexp_of_string) named
99100
]
100101
; List [ Atom "unnamed"; sexp_of_list sexp_of_string unnamed ]
102+
; List [ Atom "full_splat"; (sexp_of_option sexp_of_string) full_splat ]
101103
]
102104
;;
103105

104106
let equal = ( = )
105107
let pp fmt t = Sexp.pp_hum fmt (sexp_of_t t)
106108
let named t name = List.assoc name t.named
107109
let unnamed t = t.unnamed
108-
let empty = { named = []; unnamed = [] }
109110

110-
let create route captured =
111+
let splat t =
112+
match t.full_splat with
113+
| None -> t.unnamed
114+
| Some r -> t.unnamed @ String.split_on_char ~sep:'/' r
115+
;;
116+
117+
let full_splat t = t.full_splat
118+
let empty = { named = []; unnamed = []; full_splat = None }
119+
120+
let create route captured (remainder : string option) =
111121
let rec loop acc (route : Route.t) captured =
112122
match route, captured with
113-
| Full_splat, [] -> acc
123+
| Full_splat, [] -> { acc with full_splat = remainder }
114124
| Nil, [] -> acc
115125
| Literal (_, route), _ -> loop acc route captured
116126
| Param (None, route), p :: captured ->
@@ -119,7 +129,7 @@ module Params = struct
119129
| Param (Some name, route), p :: captured ->
120130
let acc = { acc with named = (name, p) :: acc.named } in
121131
loop acc route captured
122-
| Full_splat, rest -> { acc with unnamed = List.rev_append rest acc.unnamed }
132+
| Full_splat, _ :: _ -> assert false
123133
| Param (_, _), [] -> assert false
124134
| Nil, _ :: _ -> assert false
125135
in
@@ -157,38 +167,89 @@ let rec sexp_of_t f t =
157167
let empty_with data = Node { data; literal = Smap.empty; param = None }
158168
let empty = empty_with None
159169

170+
module Tokens : sig
171+
type t
172+
173+
val create : string -> t
174+
val next : t -> (t * string) option
175+
val remainder : t -> string option
176+
end = struct
177+
type t =
178+
{ start : int
179+
; s : string
180+
}
181+
182+
let create s =
183+
if s = ""
184+
then { s; start = 0 }
185+
else if s.[0] = '/'
186+
then { s; start = 1 }
187+
else { s; start = 0 }
188+
;;
189+
190+
let remainder t =
191+
let len = String.length t.s in
192+
if t.start >= len
193+
then None
194+
else if t.start = 0
195+
then Some t.s
196+
else (
197+
let res = String.sub t.s ~pos:t.start ~len:(len - t.start) in
198+
Some res)
199+
;;
200+
201+
let next t =
202+
let len = String.length t.s in
203+
if t.start >= len
204+
then None
205+
else (
206+
match String.index_from_opt t.s t.start '/' with
207+
| None ->
208+
let res =
209+
let len = len - t.start in
210+
String.sub t.s ~pos:t.start ~len
211+
in
212+
Some ({ t with start = len }, res)
213+
| Some j ->
214+
let res =
215+
let len = j - t.start in
216+
String.sub t.s ~pos:t.start ~len
217+
in
218+
Some ({ t with start = j + 1 }, res))
219+
;;
220+
end
221+
160222
let match_url t url =
161-
let tokens = String.split_on_char ~sep:'/' url in
162-
match tokens with
163-
| "" :: tokens ->
164-
let accept a route captured =
165-
let params = Params.create route (List.rev captured) in
166-
Some (a, params)
167-
in
168-
let rec loop t captured tokens =
169-
match t with
170-
| Accept (a, route) -> accept a route (List.rev_append tokens captured)
171-
| Node t ->
172-
(match tokens with
173-
| [ "" ] | [] ->
174-
(match t.data with
223+
let tokens = Tokens.create url in
224+
let accept a route captured remainder =
225+
let params = Params.create route (List.rev captured) remainder in
226+
Some (a, params)
227+
in
228+
let rec loop t captured (tokens : Tokens.t) =
229+
match t with
230+
| Accept (a, route) ->
231+
let remainder = Tokens.remainder tokens in
232+
accept a route captured remainder
233+
| Node t ->
234+
(match Tokens.next tokens with
235+
| None ->
236+
(match t.data with
237+
| None -> None
238+
| Some (a, route) -> accept a route captured None)
239+
| Some (tokens, s) ->
240+
let param =
241+
match t.param with
175242
| None -> None
176-
| Some (a, route) -> accept a route captured)
177-
| s :: tokens ->
178-
let param =
179-
match t.param with
180-
| None -> None
181-
| Some node -> loop node (s :: captured) tokens
182-
in
183-
(match param with
184-
| Some _ -> param
185-
| None ->
186-
(match Smap.find_opt s t.literal with
187-
| None -> None
188-
| Some node -> (loop [@tailcall]) node captured tokens)))
189-
in
190-
loop t [] tokens
191-
| _ -> None
243+
| Some node -> loop node (s :: captured) tokens
244+
in
245+
(match param with
246+
| Some _ -> param
247+
| None ->
248+
(match Smap.find_opt s t.literal with
249+
| None -> None
250+
| Some node -> (loop [@tailcall]) node captured tokens)))
251+
in
252+
loop t [] tokens
192253
;;
193254

194255
let match_route t route =

opium/src/router.mli

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,26 @@ module Params : sig
4040
(** Extract a single named parameter *)
4141
val named : t -> string -> string
4242

43+
(** only for testing *)
4344
val all_named : t -> (string * string) list
4445

4546
(** Only for testing *)
46-
val make : named:(string * string) list -> unnamed:string list -> t
47+
val make
48+
: named:(string * string) list
49+
-> unnamed:string list
50+
-> full_splat:string option
51+
-> t
4752

48-
(** Etract all unnamed "**" parameters in order *)
53+
(** Etract all unnamed "*" parameters in order *)
4954
val unnamed : t -> string list
5055

56+
(** [full_splat t] returns the raw string matched by "**". *)
57+
val full_splat : t -> string option
58+
59+
(** [splat t] extracts unnamed + full_splat in a single list. This is present to match
60+
the old routing behavior *)
61+
val splat : t -> string list
62+
5163
val sexp_of_t : t -> Sexp.t
5264
val equal : t -> t -> bool
5365
val pp : Format.formatter -> t -> unit

opium/test/opium_router_tests.ml

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ let%expect_test "we can add & match literal routes" =
7575
let router = add empty route () in
7676
test_match_url router url;
7777
[%expect {|
78-
matched with params: ((named ()) (unnamed ())) |}]
78+
matched with params: ((named ()) (unnamed ()) (full_splat ())) |}]
7979
;;
8080
8181
let%expect_test "we can extract parameter after match" =
@@ -84,9 +84,8 @@ let%expect_test "we can extract parameter after match" =
8484
test_match_url router "/foo/100/baz";
8585
test_match_url router "/foo/100";
8686
test_match_url router "/foo/100/200/300";
87-
[%expect
88-
{|
89-
matched with params: ((named ((bar baz))) (unnamed (100)))
87+
[%expect {|
88+
matched with params: ((named ((bar baz))) (unnamed (100)) (full_splat ()))
9089
no match
9190
no match |}]
9291
;;
@@ -112,7 +111,7 @@ let%expect_test "ambiguity in routes" =
112111
(Failure "duplicate routes")
113112
Raised at Stdlib.failwith in file "stdlib.ml", line 29, characters 17-33
114113
Called from Stdlib__list.fold_left in file "list.ml", line 121, characters 24-34
115-
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 104, characters 2-49
114+
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 135, characters 2-49
116115
Called from Expect_test_collector.Make.Instance.exec in file "collector/expect_test_collector.ml", line 244, characters 12-19 |}]
117116
;;
118117
@@ -128,7 +127,7 @@ let%expect_test "ambiguity in routes 2" =
128127
(Failure "duplicate routes")
129128
Raised at Stdlib.failwith in file "stdlib.ml", line 29, characters 17-33
130129
Called from Stdlib__list.fold_left in file "list.ml", line 121, characters 24-34
131-
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 120, characters 2-43
130+
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 151, characters 2-43
132131
Called from Expect_test_collector.Make.Instance.exec in file "collector/expect_test_collector.ml", line 244, characters 12-19 |}]
133132
;;
134133
@@ -144,7 +143,8 @@ let%expect_test "nodes are matched correctly" =
144143
let test = test_match router in
145144
test "/foo/bar" "Wrong";
146145
test "/foo/baz" "Right";
147-
[%expect {| |}]
146+
[%expect
147+
{| |}]
148148
;;
149149
150150
let%expect_test "full splat node matches" =
@@ -153,11 +153,10 @@ let%expect_test "full splat node matches" =
153153
test "/foo/bar";
154154
test "/foo/bar/foo";
155155
test "/foo/";
156-
[%expect
157-
{|
158-
matched with params: ((named ()) (unnamed (bar)))
159-
matched with params: ((named ()) (unnamed (bar foo)))
160-
matched with params: ((named ()) (unnamed (""))) |}]
156+
[%expect {|
157+
matched with params: ((named ()) (unnamed ()) (full_splat (bar)))
158+
matched with params: ((named ()) (unnamed ()) (full_splat (bar/foo)))
159+
matched with params: ((named ()) (unnamed ()) (full_splat ())) |}]
161160
;;
162161
163162
let%expect_test "full splat + collision checking" =
@@ -172,18 +171,17 @@ let%expect_test "full splat + collision checking" =
172171
(Failure "duplicate routes")
173172
Raised at Stdlib.failwith in file "stdlib.ml", line 29, characters 17-33
174173
Called from Stdlib__list.fold_left in file "list.ml", line 121, characters 24-34
175-
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 164, characters 9-45
174+
Called from Opium_tests__Opium_router_tests.(fun) in file "opium/test/opium_router_tests.ml", line 211, characters 9-45
176175
Called from Expect_test_collector.Make.Instance.exec in file "collector/expect_test_collector.ml", line 244, characters 12-19 |}]
177176
;;
178177
179178
let%expect_test "two parameters" =
180179
let router = of_routes' [ "/test/:format/:name/:baz" ] in
181180
let test = test_match_url router in
182181
test "/test/json/bar/blah";
183-
[%expect
184-
{|
182+
[%expect {|
185183
matched with params: ((named ((baz blah) (name bar) (format json)))
186-
(unnamed ())) |}]
184+
(unnamed ()) (full_splat ())) |}]
187185
;;
188186
189187
let%expect_test "full splat" =
@@ -194,11 +192,11 @@ let%expect_test "full splat" =
194192
test "/";
195193
test "";
196194
test "/user/123/foo/bar";
197-
[%expect
198-
{|
199-
matched with params: ((named ()) (unnamed (test)))
200-
matched with params: ((named ()) (unnamed (test "")))
201-
matched with params: ((named ()) (unnamed ("")))
202-
matched with params: ((named ()) (unnamed ()))
203-
matched with params: ((named ()) (unnamed (user 123 foo bar))) |}]
195+
[%expect{|
196+
matched with params: ((named ()) (unnamed ()) (full_splat (test)))
197+
matched with params: ((named ()) (unnamed ()) (full_splat (test/)))
198+
matched with params: ((named ()) (unnamed ()) (full_splat ()))
199+
matched with params: ((named ()) (unnamed ()) (full_splat ()))
200+
matched with params: ((named ()) (unnamed ())
201+
(full_splat (user/123/foo/bar))) |}]
204202
;;

opium/test/route.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ module Route = struct
1414

1515
let pp fmt { params; splat } =
1616
let sexp =
17-
Router.Params.make ~named:params ~unnamed:splat |> Router.Params.sexp_of_t
17+
Router.Params.make ~named:params ~unnamed:splat ~full_splat:None
18+
|> Router.Params.sexp_of_t
1819
in
1920
Sexp.pp_hum fmt sexp
2021
;;

0 commit comments

Comments
 (0)