Library
Module
Module type
Parameter
Class
Class type
'a t
represents a Trie based router. Pretty printing/debugging a router
('a, 'b) uri
represents a route URI - both the path and query, e.g. /home/about/,
/home/contact, /home/contact?name=a&no=123
etc.
method'
represents HTTP request methods. It can be used as part of a uri
in %wtr
ppx.
Represents a uri component decoder, such as :int, :float, :bool
etc.
create routes
creates a router from a list of route
s. Values of routes
are created by %wtr
ppx.
A full example demonstrating creating a router, route and route handlers:
module Fruit = struct
type t = Apple | Orange | Pineapple
let t : t Wtr.decoder =
Wtr.create_decoder ~name:"fruit" ~decode:(function
| "apple" -> Some Apple
| "orange" -> Some Orange
| "pineapple" -> Some Pineapple
| _ -> None )
end
(* Route handlers. *)
let about_page = "about page"
let prod_page i = "Int page. number : " ^ string_of_int i
let float_page f = "Float page. number : " ^ string_of_float f
let contact_page nm num =
"Contact. Hi, " ^ nm ^ ". Num " ^ string_of_int num
let product1 name id q =
Format.sprintf "Product1 %s. Id: %d. q = %b" name id q
let product2 name id = Format.sprintf "Product2 %s. Id: %d." name id
let fruit_page = function
| Fruit.Apple -> "Apples are juicy!"
| Orange -> "Orange is a citrus fruit."
| Pineapple -> "Pineapple has scaly skin"
let faq category_id =
let category_name =
match category_id with
| 1 -> "products"
| 2 -> "insurance"
| 3 -> "returns"
| _ -> "unknown"
in
"FAQ page for category : " ^ category_name
let router =
Wtr.(
create
[ {%wtr| get,post,head,delete ; /home/about/ |} about_page
; {%wtr| head,delete ; /home/:int/ |} prod_page
; {%wtr| get,post ; /home/:float/ |} float_page
; {%wtr| get; /contact/*/:int |} contact_page
; {%wtr| get; /product/:string?section=:int&q=:bool |} product1
; {%wtr| get; /product/:string?section=:int&q1=yes |} product2
; {%wtr| get; /fruit/:Fruit |} fruit_page
; {%wtr| GET; /faq/:int/** |} faq ])
match method' uri t
matches a route to a given uri
and method'
, executes its handler and returns the computed value. None
is returned if both uri
and method'
are not matched.
Examples of calling match'
and its results:
let () =
Format.(fprintf std_formatter "@.@.====Router Match Results====@.") ;
[ Wtr.match' `GET "/home/100001.1/" router
; Wtr.match' `DELETE "/home/100001/" router
; Wtr.match' `GET "/home/about/" router
; Wtr.match' `GET "/product/dyson350?section=233&q=true" router
; Wtr.match' `GET "/product/dyson350?section=2&q=false" router
; Wtr.match' `GET "/product/dyson350?section=2&q1=yes" router
; Wtr.match' `GET "/product/dyson350?section=2&q1=no" router
; Wtr.match' `GET "/fruit/apple" router
; Wtr.match' `GET "/fruit/orange" router
; Wtr.match' `GET "/fruit/pineapple" router
; Wtr.match' `GET "/fruit/guava" router
; Wtr.match' `GET "/faq/1/" router
; Wtr.match' `GET "/faq/1/whatever" router
; Wtr.match' `GET "/faq/2/whateasdfasdfasdf" router ]
|> List.iteri (fun i -> function
| Some s -> Printf.printf "%3d: %s\n" (i + 1) s
| None -> Printf.printf "%3d: None\n" (i + 1) )
The match call results in the following results:
====Router Match Results====
1: Float page. number : 100001.1
2: Int page. number : 100001
3: about page
4: Product1 dyson350. Id: 233. q = true
5: Product1 dyson350. Id: 2. q = false
6: Product2 dyson350. Id: 2.
7: None
8: Apples are juicy!
9: Orange is a citrus fruit.
10: Pineapple has scaly skin
11: None
12: FAQ page for category : products
13: FAQ page for category : products
14: FAQ page for category : insurance
Specifying a URI in a %wtr
ppx follows the following syntax:
wtr uri spec = http methods separated by comma ';' http uri
A URI in a %wtr
ppx is syntactically and sematically a HTTP URI with the addition of decoders and some some useful additions listed below.
**
- Full spat operator matches any/all path following a full splat. For example in /home/**
matches the following uri paths, /home/about/, home/contact, /home/product
etc. Full splat must be the last component of an uri. It is an error to specify other uri path component after full splat operator.*
- A wildcard operator matches any text appearing on the path component position. For example, uri /home/*/page1
matches the following /home/23/page1, /home/true/page1, /home/234.4/page1
etc. The semantics of wildcard operator is the same as using :string
decoder in a uri, i.e. it affects the route handler function signature./
- A trailing slash ensures that Wtr will match a trailing /
in a uri. For example, uri /home/about/
matches /home/about/
but not /home/about
.Wtr
provides the following built in decoders that can be used as when specifying wtr URI in {%wtr| |}
ppx:
:int
- decodes a int
:int32
- decodes a int32
:int64
- decodes a int64
:float
- decodes a float
or int
:bool
- decodes a bool
:string
- decodes a string
The built-in decoders can be used as follows:
{%wtr|get; /home/:int |}
, {%wtr| /home/:bool |}
Wtr also supports creating custom, user defined decoders. The convention for user defined decoders is as follows:
It should be defined in a module. The module should define a type called t
and a value called t
which returns t Wtr.decoder
.
Example of defining custom decoder:
module Fruit = struct
type t = Apple | Orange | Pineapple
let t : t Wtr.decoder =
Wtr.create_decoder ~name:"fruit" ~decode:(function
| "apple" -> Some Apple
| "orange" -> Some Orange
| "pineapple" -> Some Pineapple
| _ -> None )
end
The custom decoder then can be used in %wtr
ppx as follows,
{%wtr| get ; /fruit/:Fruit |} fruit_page
Usage of decoders in a URI directly affect the function signature of a route handler. For e.g.
/home/:int/:bool
expects a route handler as fun (i:int) (b:bool) -> ....
/home/:string
expects a route handler as (fun (s:string) -> ...)
val create_decoder : name:string -> decode:(string -> 'a option) -> 'a decoder
create_decoder ~name ~decode
creates a user defined decoder uri component. name
is used during the pretty printing of uri
.
val pp : Format.formatter -> 'a t -> unit
pp fmt t
pretty prints router routes. This can be useful for debugging router/routing issues as it displays hierarchially possible routes a matching engine may take in matching a given uri and method.
HTTP method names are capitalized.
Printing the router
from the example givn in create
method pretty prints the following:
GET
/home
/about
/
/:float
/
/contact
/:string
/:int
/product
/:string
/section
/:int
/q
/:bool
/q1
/yes
/fruit
/:fruit
/faq
/:int
/**
POST
/home
/about
/
/:float
/
HEAD
/home
/about
/
/:int
/
DELETE
/home
/about
/
/:int
/
val pp_method : Format.formatter -> method' -> unit
val pp_route : Format.formatter -> 'b route -> unit