package sihl-web

  1. Overview
  2. Docs

Source file flash.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
132
133
134
135
136
137
138
139
140
141
142
let log_src = Logs.Src.create "sihl.middleware.flash"

module Logs = (val Logs.src_log log_src : Logs.LOG)

type t =
  { alert : string option
  ; notice : string option
  ; custom : string option
  }

let of_yojson json =
  let open Yojson.Safe.Util in
  try
    let alert = json |> member "alert" |> to_string_option in
    let notice = json |> member "notice" |> to_string_option in
    let custom = json |> member "custom" |> to_string_option in
    Some { alert; notice; custom }
  with
  | _ -> None
;;

let to_yojson { alert; notice; custom } =
  let alert =
    alert
    |> Option.map (fun alert -> `String alert)
    |> Option.value ~default:`Null
  in
  let notice =
    notice
    |> Option.map (fun notice -> `String notice)
    |> Option.value ~default:`Null
  in
  let custom =
    custom
    |> Option.map (fun custom -> `String custom)
    |> Option.value ~default:`Null
  in
  `Assoc [ "alert", alert; "notice", notice; "custom", custom ]
;;

exception Flash_not_found

let key_alert : string option Opium.Context.key =
  Opium.Context.Key.create
    ("flash alert", Sexplib.Std.(sexp_of_option sexp_of_string))
;;

let key_notice : string option Opium.Context.key =
  Opium.Context.Key.create
    ("flash notice", Sexplib.Std.(sexp_of_option sexp_of_string))
;;

let key_custom : string option Opium.Context.key =
  Opium.Context.Key.create
    ("flash custom", Sexplib.Std.(sexp_of_option sexp_of_string))
;;

let find_with_key req key =
  (* Raising an exception is ok since we assume that before find can be called
     the middleware has been passed *)
  try Opium.Context.find_exn key req.Opium.Request.env with
  | _ ->
    Logs.err (fun m -> m "No flash storage found");
    Logs.info (fun m ->
        m
          "Have you applied the session and flash middleware for this route? \
           The flash middleware requires the session middleware.");
    raise Flash_not_found
;;

let set_with_key flash res key =
  let env = res.Opium.Response.env in
  let env = Opium.Context.add key flash env in
  { res with env }
;;

let find_alert req = find_with_key req key_alert
let find_notice req = find_with_key req key_notice
let find_custom req = find_with_key req key_custom
let set_alert alert resp = set_with_key alert resp key_alert
let set_notice notice resp = set_with_key notice resp key_notice
let set_custom custom resp = set_with_key custom resp key_custom

let decode_flash flash =
  let parsed =
    try Some (Yojson.Safe.from_string flash) with
    | _ -> None
  in
  match parsed with
  | None ->
    Logs.warn (fun m -> m "Failed to parse flash message %s" flash);
    None, None, None
  | Some parsed ->
    (match of_yojson parsed with
    | None ->
      Logs.warn (fun m -> m "Failed to decode flash message %s" flash);
      None, None, None
    | Some decoded -> decoded.alert, decoded.notice, decoded.custom)
;;

let middleware ?(flash_store_name = "flash.store") () =
  let open Lwt.Syntax in
  let filter handler req =
    let session = Session.find req in
    let* current_flash =
      Sihl_facade.Session.find_value session flash_store_name
    in
    let alert, notice, custom =
      match current_flash with
      | None -> None, None, None
      | Some current_flash -> decode_flash current_flash
    in
    let env = req.Opium.Request.env in
    let env = Opium.Context.add key_alert alert env in
    let env = Opium.Context.add key_notice notice env in
    let env = Opium.Context.add key_custom custom env in
    let req = { req with env } in
    (* User might call set() in handler *)
    let* res = handler req in
    let alert =
      Option.join (Opium.Context.find key_alert res.Opium.Response.env)
    in
    let notice =
      Option.join (Opium.Context.find key_notice res.Opium.Response.env)
    in
    let custom =
      Option.join (Opium.Context.find key_custom res.Opium.Response.env)
    in
    let next_flash =
      { alert; notice; custom } |> to_yojson |> Yojson.Safe.to_string
    in
    (* Put next flash message into flash store *)
    let* () =
      Sihl_facade.Session.set_value
        session
        ~k:flash_store_name
        ~v:(Some next_flash)
    in
    Lwt.return res
  in
  Rock.Middleware.create ~name:"session.flash" ~filter
;;
OCaml

Innovation. Community. Security.