package sihl

  1. Overview
  2. Docs

Source file web.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
type meth =
  | Get
  | Head
  | Options
  | Post
  | Put
  | Patch
  | Delete
  | Any

type handler = Rock.Request.t -> Rock.Response.t Lwt.t
type route = meth * string * handler

type router =
  { scope : string
  ; routes : route list
  ; middlewares : Rock.Middleware.t list
  }

let trailing_char s =
  let length = String.length s in
  try Some (String.sub s (length - 1) 1) with
  | _ -> None
;;

let tail s =
  try String.sub s 1 (String.length s - 1) with
  | _ -> ""
;;

let prefix prefix ((meth, path, handler) : route) =
  if String.equal path ""
  then meth, prefix, handler
  else (
    let path =
      match trailing_char prefix, Astring.String.head path with
      | Some "/", Some '/' -> Printf.sprintf "%s%s" prefix (tail path)
      | Some "/", Some _ -> Printf.sprintf "%s%s" prefix path
      | Some _, Some '/' -> Printf.sprintf "%s%s" prefix path
      | None, Some '/' -> Printf.sprintf "%s%s" prefix path
      | Some "/", None -> Printf.sprintf "%s%s" prefix path
      | _, _ -> Printf.sprintf "%s/%s" prefix path
    in
    let path = CCString.replace ~sub:"//" ~by:"/" path in
    meth, path, handler)
;;

let apply_middleware_stack
    (middleware_stack : Rock.Middleware.t list)
    ((meth, path, handler) : route)
  =
  (* The request goes through the middleware stack from top to bottom, so we
     have to reverse the middleware stack *)
  let middleware_stack = List.rev middleware_stack in
  let wrapped_handler =
    List.fold_left
      (fun handler middleware -> Rock.Middleware.apply middleware handler)
      handler
      middleware_stack
  in
  meth, path, wrapped_handler
;;

let get path ?(middlewares = []) handler =
  { scope = ""; routes = [ Get, path, handler ]; middlewares }
;;

let head path ?(middlewares = []) handler =
  { scope = ""; routes = [ Head, path, handler ]; middlewares }
;;

let options path ?(middlewares = []) handler =
  { scope = ""; routes = [ Options, path, handler ]; middlewares }
;;

let post path ?(middlewares = []) handler =
  { scope = ""; routes = [ Post, path, handler ]; middlewares }
;;

let put path ?(middlewares = []) handler =
  { scope = ""; routes = [ Put, path, handler ]; middlewares }
;;

let patch path ?(middlewares = []) handler =
  { scope = ""; routes = [ Patch, path, handler ]; middlewares }
;;

let delete path ?(middlewares = []) handler =
  { scope = ""; routes = [ Delete, path, handler ]; middlewares }
;;

let any path ?(middlewares = []) handler =
  { scope = ""; routes = [ Any, path, handler ]; middlewares }
;;

let routes_of_router ({ scope; routes; middlewares } : router) : route list =
  routes
  |> List.map (prefix scope)
  |> List.map (apply_middleware_stack middlewares)
;;

let choose ?(scope = "/") ?(middlewares = []) (routers : router list) : router =
  let scope =
    match CCString.chop_prefix ~pre:"/" scope with
    | Some prefix -> "/" ^ prefix
    | None -> "/" ^ scope
  in
  let routes = routers |> List.map routes_of_router |> List.concat in
  { scope; routes; middlewares }
;;

let externalize_path ?prefix path =
  let prefix =
    match prefix, Core_configuration.read_string "PREFIX_PATH" with
    | Some prefix, _ -> prefix
    | _, Some prefix -> prefix
    | _ -> ""
  in
  path
  |> String.split_on_char '/'
  |> List.cons prefix
  |> String.concat "/"
  |> Stringext.replace_all ~pattern:"//" ~with_:"/"
;;

(* This is a generic style that is used in various middlewares. It is based on
   the style of
   https://github.com/rgrinberg/opium/blob/master/opium/src/middlewares/middleware_debugger.ml *)
let style =
  {|/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}code,pre{font-family:monospace,monospace;font-size:1em}[type=button],[type=reset],[type=submit]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[hidden]{display:none}h2,h3,pre{margin:0}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}[role=button]{cursor:pointer}h2,h3{font-size:inherit;font-weight:inherit}code,pre{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-500{--bg-opacity:1;background-color:#a0aec0;background-color:rgba(160,174,192,var(--bg-opacity))}.bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-t{border-top-width:1px}.border-b{border-bottom-width:1px}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.items-center{align-items:center}.justify-between{justify-content:space-between}.font-semibold{font-weight:600}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.text-2xl{font-size:1.5rem}.leading-8{line-height:2rem}.leading-snug{line-height:1.375}.leading-normal{line-height:1.5}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.mt-0{margin-top:0}.mb-4{margin-bottom:1rem}.mt-6{margin-top:1.5rem}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.scrolling-touch{-webkit-overflow-scrolling:touch}.p-0{padding:0}.p-4{padding:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.relative{position:relative}.text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.subpixel-antialiased{-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:border{border-width:1px}.sm\:items-baseline{align-items:baseline}.sm\:text-3xl{font-size:1.875rem}.sm\:leading-9{line-height:2.25rem}.sm\:py-4{padding-top:1rem;padding-bottom:1rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-12{padding-top:3rem;padding-bottom:3rem}}@media (min-width:768px){.md\:text-lg{font-size:1.125rem}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}|}
;;