Source file router_trie.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
open Core
open S.Route_exceptions



module Make_with_config (Req: Cohttp.S.Request)
                        (B: S.Body)
                        (Resp: Cohttp.S.Response)
                        (IO: S.Io)
                        (Config : S.Router_config with
                                    type req := Req.t and
                                    type body := B.t and
                                    type resp := Resp.t and
                                    type 'a io := 'a IO.t) = struct    
    include Config
   
    type callback = ?vars: string String.Table.t -> Req.t -> B.t -> (Resp.t * B.t) IO.t
    type exn_handler = ?vars:string Core.String.Table.t -> exn -> (Resp.t * B.t) IO.t
    type route = string * (Cohttp.Code.meth * callback) list

    type t = { router : callback String.Table.t Path_trie.path_trie
             ; exn_handler : exn_handler
             ; fallback : callback }

    module type Routes = sig
        val routes : route list
    end

    let create ?exn_handler:(exn_handler = Config.default_exn_handler)
               ?fallback_response:(fallback_response = Config.default_fallback)
               (routes: route list) : (t, exn) Result.t  =
        let trie = Path_trie.empty () in
        let rec insert =
            function
            | [] -> Ok () 
            | (path, callbacks)::routes ->
                    begin
                        List.fold_until callbacks
                                        ~init:(String.Table.create ())
                                        ~f:(fun callback_map (meth, callback) ->
                                            let key = Cohttp.Code.string_of_method meth in
                                            match Hashtbl.add callback_map ~key:key ~data:callback with
                                            | `Ok -> Continue (callback_map)
                                            | `Duplicate -> Stop (Error meth))
                                        ~finish:(fun map -> Ok map)
                        |> function
                           | Ok callback_map -> begin
                                match Path_trie.insert_path trie path callback_map with
                                | `Ok -> insert routes
                                | `Duplicate -> Error (DuplicateRouteTemplate path)
                                end
                           | Error meth -> Error (DuplicateHttpMethod meth)
                    end in
        try
            insert routes
            |> Result.map
                ~f:(fun () ->
                    { router=trie
                    ; exn_handler=exn_handler
                    ; fallback=fallback_response }) 
        with err -> Error err 

    let create_exn ?exn_handler:(exn_handler = Config.default_exn_handler)
                   ?fallback_response:(fallback_response = Config.default_fallback)
                   (routes: route list) : t =
        match create ~exn_handler ~fallback_response routes with
        | Ok trie -> trie
        | Error err -> raise err

    let create_from_modules ?exn_handler:(exn_handler = Config.default_exn_handler)
                            ?fallback_response:(fallback_response = Config.default_fallback)
                            (modules : (module Routes) list) =
        List.map modules ~f:(fun (module R) -> R.routes)
        |> List.concat
        |> create ~exn_handler ~fallback_response

    let create_from_modules_exn ?exn_handler:(exn_handler = Config.default_exn_handler)
                                ?fallback_response:(fallback_response = Config.default_fallback)
                                (modules : (module Routes) list) =
        List.map modules ~f:(fun (module R) -> R.routes)
        |> List.concat
        |> create_exn ~exn_handler ~fallback_response

    let dispatch ({ router; exn_handler; fallback } : t) req body =
        Req.uri req
        |> Uri.path
        |> Path_trie.matches router
        |> Option.bind
            ~f:(fun (vars, callbacks) ->
                Req.meth req
                |> Cohttp.Code.string_of_method
                |> Hashtbl.find callbacks
                |> Option.map ~f:(fun callback -> vars, callback))
        |> Option.map
            ~f:(fun (vars, callback) ->
                try
                   callback ~vars:vars req body
                with err -> exn_handler ~vars:vars err)
        |> function
           | Some res -> res
           | None -> fallback req body
end

module Make (Req: Cohttp.S.Request)
            (B: S.Body)
            (Resp: Cohttp.S.Response)
            (IO: S.Io) = struct
    
    module Default_config = struct

        let default_exn_handler ?vars:_ _ = 
            IO.return (Resp.make ~status:`Internal_server_error (), B.empty)

        let default_fallback ?vars:_ _req _body =
            IO.return (Resp.make ~status:`Not_found (), B.empty)
    end

    include Make_with_config (Req) (B) (Resp) (IO) (Default_config)
end