aboutsummaryrefslogtreecommitdiff
path: root/compat/lustre_http/src/lustre_http.gleam
blob: a066faa30df478c627a9a98c2f357a65abff8f59 (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
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
import gleam/json
import lustre/effect.{Effect}

pub type HttpError {
  Unauthorized
  NotFound
  InternalServerError(String)
  OtherError(Int, String)
}

pub type StringResult =
  Result(String, HttpError)

pub type HttpOrJsonError {
  H(HttpError)
  J(json.DecodeError)
}

pub type JsonResult(t) =
  Result(t, HttpOrJsonError)

external fn do_request(
  method: String,
  url: String,
  body: String,
  on_success: fn(String) -> Nil,
  on_error: fn(Int, String) -> Nil,
) -> Nil =
  "./ffi.mjs" "do_request"

fn do_get(url, on_success, on_error) {
  do_request("GET", url, "", on_success, on_error)
}

fn do_post(url, body, on_success, on_error) {
  do_request("POST", url, body, on_success, on_error)
}

/// ###  Usage
/// ```
/// import lustre_http as http
///
/// type Msg {
///   SomeInteraction
///   TextReceived(StringResult)
/// }
///
/// pub fn update(model, msg) {
///   case msg {
///     SomeInteraction -> #(model, http.get_as_text("the_text", TextReceived))
///     TextReceived(Ok(text)) -> #(apply(text, model), effect.none())
///     TextReceived(Error(e)) -> #(indicate_problem(e, model), effect.none())
///   }
/// }
/// ```
pub fn get_as_text(url: String, to_msg: fn(StringResult) -> msg) -> Effect(msg) {
  use dispatch <- effect.from

  do_get(
    url,
    fn(body) { dispatch(to_msg(Ok(body))) },
    fn(code, msg) {
      decode_error(code, msg)
      |> Error
      |> to_msg
      |> dispatch
    },
  )
}

fn decode_error(code, msg) {
  case code {
    401 -> Unauthorized
    404 -> NotFound
    500 -> InternalServerError(msg)
    _ -> OtherError(code, msg)
  }
}

/// Will automatically try to decode the JSON for you.
/// The error-type is a bit complicated. In a future version will probably
/// (a) force "text/json" in the headers (b) not decode for you.
pub fn get_as_json(
  url: String,
  to_msg: fn(JsonResult(String)) -> msg,
  decoder,
) -> Effect(msg) {
  use dispatch <- effect.from

  do_get(
    url,
    fn(body) {
      case json.decode(from: body, using: decoder) {
        Ok(json) -> dispatch(to_msg(Ok(json)))
        Error(json_error) -> dispatch(to_msg(Error(J(json_error))))
      }
    },
    fn(code, msg) {
      let http_error = case code {
        401 -> Unauthorized
        404 -> NotFound
        500 -> InternalServerError(msg)
        _ -> OtherError(code, msg)
      }
      dispatch(to_msg(Error(H(http_error))))
    },
  )
}

/// Future versions will force headers in both the request and the response
/// so you can post json and receive text, post text and receive json, etc.
pub fn post_text(url: String, body: String, to_msg: fn(StringResult) -> msg) {
  use dispatch <- effect.from

  do_post(
    url,
    body,
    fn(body) { dispatch(to_msg(Ok(body))) },
    fn(code, msg) {
      decode_error(code, msg)
      |> Error
      |> to_msg
      |> dispatch
    },
  )
}