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_client_addr(
in: minc.Incoming(state),
then: fn(addr.SocketAddr) -> Result(
response.Response(mist.ResponseData),
fail.Failure,
),
) -> Result(response.Response(mist.ResponseData), fail.Failure)
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.