messua/handle

For dealing with Incoming requests.

Remember that Outgoing responses are actually Results, so you can use use to simplify your happy path.


fn handle_request(in: Incoming(MyAppState)) -> Outgoing {
  use action <- handle.require_header(in, "x-my-app-action")
  use payload <- handle.require_json_body(in, 1048576, myapp.payload_decoder())
  use app_state <- minc.require_state(in)

  let #(new_app_state, response_payload) =
    myapp.do_business_logic(app_state, payload)
  let response_body = myapp.response_payload_to_json(response_payload)
  minc.set_state(in, new_app_state)

  mout.ok()
  |> mout.with_json_body(response_body)
  |> mout.with_header("x-my-app-result", "peachy")
}

Functions in this module that start with get_ return Options, functions that start with require_ return Result(a, Failure) and are meant for use sugar.

Types

A Handler(state) is a function that handles an Incoming request injected with some state and produces an Outgoing response.

pub type Handler(state) =
  fn(minc.Incoming(state)) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  )

Values

pub fn get_body(
  in: minc.Incoming(state),
  size_limit: Int,
) -> option.Option(BitArray)

Get the body of the request, if there is one.

pub fn get_client_addr(
  in: minc.Incoming(state),
) -> option.Option(addr.SocketAddr)
pub fn get_cookie(
  in: minc.Incoming(state),
  cookie_name: String,
) -> option.Option(String)
pub fn get_header(
  in: minc.Incoming(state),
  name: String,
) -> option.Option(String)

Return the value of the name header from the request, if it exists.

pub fn get_query_string(
  in: minc.Incoming(state),
) -> option.Option(String)

Return the query string portion of the request URL, if there is one.

pub fn get_query_value(
  in: minc.Incoming(string),
  key: String,
) -> option.Option(String)
pub fn get_state(in: minc.Incoming(state)) -> Result(state, Nil)
pub fn get_string_body(
  in: minc.Incoming(state),
  size_limit: Int,
) -> option.Option(String)

If there is a valid UTF-8 body, return it as a string, otherwise return None.

pub fn get_then_update_state(
  in: minc.Incoming(state),
  update: fn(state) -> state,
) -> Result(state, Nil)
pub fn get_updated_state(
  in: minc.Incoming(state),
  update: fn(state) -> state,
) -> Result(state, Nil)
pub fn get_valid_header(
  in: minc.Incoming(state),
  name: String,
  parser: fn(String) -> Result(t, Nil),
) -> option.Option(t)
pub fn headers(
  in: minc.Incoming(state),
) -> List(#(String, String))

A list of the #(header_name, header_value) pairs from the incoming request.

fn first(tuple: #(a, a)) -> a {
   tuple.0
}


pub fn handle(in: Incoming) -> Outgoing {
  let header_names = handle.headers(in) |> list.map(first)
  let body =
    ["Your request had the following headers:", ..header_names]
    |> string.join("\n")

  mout.ok() |> mout.with_string_body(body)
}
pub fn method(in: minc.Incoming(state)) -> http.Method

The method used in the incoming request.

pub fn handler(in: Incoming(Nil)) -> Outgoing {
  let method = case handle.method(in) {
    http.Get -> "GET"
    _ -> "do something more complicated than a GET with"
  }

  let body = ["You asked me to ", method, " a resource."]
  |> string.concat()

  mout.ok()
  |> mout.with_string_body(body)
}
pub fn path(in: minc.Incoming(state)) -> String

The path portion of the URL used in the incoming request.

pub fn query_pairs(
  in: minc.Incoming(state),
) -> List(#(String, String))

Decode the request query string and return it as a list of #(name, value) tuples.

This will percent-decode the key-value pairs, and no query string will result in an empty list.

This handler Function

fn handle(in: Incoming(Nil)) -> Outgoing {
  let query_pairs = handle.query_pairs(in) |> string.inspect() <> "\n"

  mout.ok() |> mout.with_string_body(query_pairs)
}

Will exhibit the following behavior:

$ curl "http://localhost:8080?frog=blue&salamander=orange"
[#("frog", "blue"), #("salamander", "orange")]

$ curl "http://localhost:8080?bird+name=lu+lu"
[#("bird name", "lu lu")]

$ curl "http://localhost:8080"
[]
pub fn require_body(
  in: minc.Incoming(state),
  size_limit: Int,
  then: fn(BitArray) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Will supply the body of the request if there is one, otherwise it will return a 400 response.

use body_bits <- handle.require_body(in, 1024 * 1024)

case do_something_with_bits(body_bits) {
  Ok(answer_string) -> mout.ok() |> mout.with_string_body(answer_string)
  Error(problem_string) -> mout.fail() |> mout.with_body_string(problem_string)
}
pub fn require_cookie(
  in: minc.Incoming(state),
  cookie_name: String,
  then: fn(String) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_header(
  in: minc.Incoming(state),
  name: String,
  then: fn(String) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Get the value of the name header from the request, or return a “Bad Request” response with an error message.

Given this handler function:

fn handle(in: Incoming(Nil)) -> Outgoing {
  use frog_color <- handle.require_header(in, "x-frog-color")
  let body = ["You prefer ", frog_color, " frogs.\n"] |> string.concat()

  mout.ok()
  |> mout.with_string_body(body)
}

You should see this behavior:

$ curl -s -D - http://localhost:8080
HTTP/1.1 400 Bad Request
content-length: 54
content-type: text/plain
date: Fri, 19 Dec 2025 21:14:21 GMT
connection: keep-alive

Request requires a valid "x-frog-color" header value.

$ curl -s -D - http://localhost:8080 -H "x-frog-color: blue"
HTTP/1.1 200 OK
content-type: text/plain
content-length: 23
date: Fri, 19 Dec 2025 21:14:11 GMT
connection: keep-alive

You prefer blue frogs.
pub fn require_json_body(
  in: minc.Incoming(state),
  size_limit: Int,
  decoder: decode.Decoder(t),
  then: fn(t) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Require a JSON-encoded body that can be decoded successfully by the supplied decoder. If there’s no body, or it fails to decode, a 400 response will be returned.

pub fn require_query_pairs(
  in: minc.Incoming(state),
  then: fn(List(#(String, String))) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_query_string(
  in: minc.Incoming(state),
  then: fn(String) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

Supply the query string portion of the request URL, or return a 400 response.

pub fn require_query_value(
  in: minc.Incoming(string),
  key: String,
  then: fn(String) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_state(
  in: minc.Incoming(state),
  then: fn(state) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_state_then_update(
  in: minc.Incoming(state),
  update: fn(state) -> state,
  then: fn(state) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_string_body(
  in: minc.Incoming(state),
  size_limit: Int,
  then: fn(String) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

If there is a valid UTF-8 body, supply it as a string, otherwise return a 400 response.

pub fn require_updated_state(
  in: minc.Incoming(state),
  update: fn(state) -> state,
  then: fn(state) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
pub fn require_valid_header(
  in: minc.Incoming(state),
  name: String,
  parser: fn(String) -> Result(t, Nil),
  then: fn(t) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

If the header with the supplied name exists, and the associated value returns an Ok() result from the supplied parser, will use that value. Otherwise, will return an error response with an explanatory message.

For example, this hander:

fn handle(in: Incoming(Nil)) -> Outgoing {
  use frog_count <- handle.require_valid_header(in, "x-frog-count", int.parse)
  let body =
    ["You requested ", int.to_string(frog_count), " frogs.\n"]
    |> string.concat()

  mout.ok() |> mout.with_string_body(body)
}

Should produce this behavior:

~/ $ curl -s -D - http://localhost:8080
HTTP/1.1 400 Bad Request
content-length: 54
content-type: text/plain
date: Tue, 23 Dec 2025 17:39:10 GMT
connection: keep-alive

Request requires a valid "x-frog-count" header value.

~/ $ curl -s -D - http://localhost:8080 -H "x-frog-count: 7"
HTTP/1.1 200 OK
content-type: text/plain
content-length: 23
date: Tue, 23 Dec 2025 17:39:13 GMT
connection: keep-alive

You requested 7 frogs.

~/ $ curl -s -D - http://localhost:8080 -H "x-frog-count: seven"
HTTP/1.1 400 Bad Request
content-length: 54
content-type: text/plain
date: Tue, 23 Dec 2025 17:39:16 GMT
connection: keep-alive

Request requires a valid "x-frog-count" header value.
pub fn require_valid_optional_header(
  in: minc.Incoming(state),
  name: String,
  parser: fn(String) -> Result(t, Nil),
  then: fn(option.Option(t)) -> Result(
    response.Response(mist.ResponseData),
    fail.Failure,
  ),
) -> Result(response.Response(mist.ResponseData), fail.Failure)

If the given header name is present and returns an Ok() value from parser(), the handling function will be furnished with Some(value); if parser() returns an Error(), messua will return an appropriate error response. If the header name is not present, the handler will be furnished with None.

Yes, this is a confusing function name, and probalby a confusing explanation. Here’s an example.

This request handler:

fn handle(in: Incoming(Nil)) -> Outgoing {
  use maybe_frog_count <- handle.require_valid_optional_header(
    in,
    "x-frog-count",
    int.parse,
  )

  let message = case maybe_frog_count {
    None -> "You don't want any frogs?"
    Some(n) if n < 0 -> "You want... us to take frogs from you?"
    Some(1) -> "You requested a single frog."
    Some(n) -> "You requested " <> int.to_string(n) <> " frogs."
  }

  mout.ok() |> mout.with_string_body(message)
}

Should exhibit this behavior:

$ curl http://localhost:8080
You don't want any frogs?

$ curl http://localhost:8080 -H "x-frog-count: none"
"none" is not a valid "x-frog-count" value.

$ curl http://localhost:8080 -H "x-frog-count: 12"
You requested 12 frogs.
pub fn scheme(in: minc.Incoming(state)) -> http.Scheme

The scheme used in the incoming request.

Search Document