summaryrefslogtreecommitdiff
path: root/lib/router.ml
blob: 03e11f3b46634b46dfe7df836b7cddf43cb6b95c (plain)
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
(* Import the StdLabels module which provides labeled versions of standard library functions *)
open StdLabels

(* Import the Routes library for type-safe HTTP routing *)
open Routes

(* Import Piaf for HTTP types like Method.t *)
open Piaf

(* Create a custom Map module keyed by HTTP methods (GET, POST, etc.) *)
(* This allows us to organize routes by their HTTP verb *)
module R = Map.Make (struct
    (* The key type is Piaf's Method.t (represents HTTP methods) *)
    type t = Method.t

    (* Define how to compare two HTTP methods for ordering in the map *)
    let compare a b =
      (* Convert methods to strings (e.g., "GET", "POST") *)
      let a_str = Method.to_string a in
      let b_str = Method.to_string b in
      (* Use standard string comparison *)
      String.compare a_str b_str
    ;;
  end)

let static path _db _req =
  let pat = Parts.wildcard_match path in
  match Body.sendfile pat with
  | Error err -> Error err
  | Ok body ->
    (match Body.to_string body with
     | Error e -> Error e
     | Ok str -> Ok (Response.of_string ~body:str Status.(`Accepted)))
;;

(* match Parts.wildcard_match path with *)
(* | "styles.css" -> Ok (Response.of_string ~body:"" Status.(`Accepted)) *)
(* | _ -> Ok (Response.of_string ~body:"" Status.(`Accepted)) *)

(* Define all routes in the application *)
let routes =
  (* Use fold_left to build up a map of routes *)
  List.fold_left
  (* For each (verb, route) pair, add the route to the map under that verb *)
    ~f:(fun acc (v, r) -> R.add_to_list v r acc) (* Start with an empty map *)
    ~init:R.empty
    (* List of (HTTP method, route pattern -> handler) tuples *)
    [ `GET, (s "posts" /? nil) @--> Handler.get_posts
      (* `GET - HTTP GET method *)
      (* s "posts" - matches the literal string "posts" *)
      (* /? - path concatenation operator *)
      (* nil - end of path (no more segments) *)
      (* @--> - binds the route pattern to the handler function *)
      (* Handler.get_posts - the function that handles this route *)
      (* ; `GET, s "assets" wildcard @--> static *)
    ; `GET, (s "posts" / int /? nil) @--> Handler.get_post
      (* / int - captures an integer parameter (post ID) *)
    ; `GET, (s "comments" / int /? nil) @--> Handler.get_comment
      (* Get a single comment by ID *)
    ; `GET, (s "users" / str / s "comments" /? nil) @--> Handler.get_user_comments
      (* / str - captures a string parameter (username) *)
    ; `GET, (s "posts" / int / s "comments" /? nil) @--> Handler.get_post_comments
      (* Get all comments for a specific post *)
    ; `GET, (s "comments" / int / s "children" /? nil) @--> Handler.get_comment_children
      (* Get child comments (replies) for a comment *)
    ; `POST, (s "posts" /? nil) @--> Post_handlers.create_post (* Create a new post *)
    ; `POST, (s "comments" /? nil) @--> Post_handlers.create_comment
      (* Create a new comment *)
    ; `POST, (s "votes" /? nil) @--> Post_handlers.create_vote
      (* Create a new vote *)

      (* bs5 routes *)
      (* root *)
      (* ; `GET, nil @--> Html.render @@ Pages.BlogIndex.make () *)
    ; `GET, nil @--> Html.render_conn @@ Pages.BlogIndex.make
    ; `GET, (s "l" /? nil) @--> Html.get_root
    ]
;;

(* Transform the routes map by applying 'one_of' to each list of routes *)
(* 'one_of' combines multiple routes into a single router that tries each in order *)
let router = R.map one_of routes

(* Function to match an incoming request against our routes *)
let match_route verb path =
  (* Try to find routes for the given HTTP verb in our map *)
  match R.find_opt verb router with
  (* If we have routes for this verb, try to match the path *)
  | Some router ->
    (* Use the Routes library's match' function to find a matching route *)
    (match match' router ~target:path with
     (* Exact match - the path matches completely *)
     | FullMatch r -> Some r
     (* Match with trailing slash - e.g., "/posts" matches "/posts/" *)
     | MatchWithTrailingSlash r -> Some r
     (* No routes matched the path *)
     | NoMatch -> None)
  (* We don't have any routes for this HTTP verb *)
  | None -> None
;;