messua/handle
Helper functions for use in handlers.
- The functions in this module whose names start with
require_
are intended foruse
expressions. - The functions in this module whose names start with
get_
returnOption
s. - Functions whose names are just nouns will always return valid (although possibly empty or meaningless) values.
Types
pub type IpAddr {
Ipv4(Int, Int, Int, Int)
Ipv6(Int, Int, Int, Int, Int, Int, Int, Int)
}
Constructors
-
Ipv4(Int, Int, Int, Int)
-
Ipv6(Int, Int, Int, Int, Int, Int, Int, Int)
Constants
Functions
pub fn cookies(req: MRequest(a)) -> List(String)
Return a list of all Set-Cookie
header values.
pub fn fmt_sock_addr(addr: SockAddr) -> String
pub fn get_client(mreq: MRequest(a)) -> Option(SockAddr)
Return the client’s SockAddr
(if possible).
pub fn get_header(
req: MRequest(a),
name: String,
) -> Option(String)
pub fn get_port(req: MRequest(a)) -> Option(Int)
Gets the port to which the request was sent, not the port of the
client’s socket (use get_client()
for that).
pub fn header_map(req: MRequest(a)) -> Dict(String, String)
Returns all request headers as a Dict
that maps header names to
header values.
Note
This will not produce the right value for multiple Set-Cookie
headers;
if you need to handle Set-Cookie
headers, use cookies()
.
pub fn headers(req: MRequest(a)) -> List(#(String, String))
Returns all request headers as a list of #(name, value)
tuples.
pub fn path_segments(req: MRequest(a)) -> List(String)
Return the (non-empty) segments of a request path.
pub fn require_body(
req: MRequest(a),
size_limit: Int,
next: fn(BitArray) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Extract the raw bytes of the body of the request, or return a 400 with a message if the body is absent.
Examples
import messua/handle
import messua/err
import messua/ok
const max_body_size: Int = 0x100000 // 1 MiB
fn process_request(req: MRequest(state)) -> MResponse {
use body_bytes <- handle.require_body(req, max_body_size)
case process_body(body_bytes) {
Ok(result) -> ok.ok()
|> ok.with_binary_body(result)
Error(e) -> err.new(500)
|> err.with_message(["unable to process request: ", string.inspect(e)])
}
}
pub fn require_client(
mreq: MRequest(a),
next: fn(SockAddr) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Requires the client’s SockAddr
to be discernable, or returns a
500 error w/a log message attached.
pub fn require_header(
req: MRequest(a),
name: String,
next: fn(String) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Get the given header value, or return a 400 response with a message if not present.
Examples
import gleam/result
import messua/handle
import messua/ok
fn retrieve_user_data(req: MRequest) -> MResponse {
use uname <- handle.require_header(req, "x-user-name")
use user <- result.try(
get_user_by_name(uname)
|> result.replace_error(err.new(500))
)
let body = encode_user_details(user)
ok.ok()
|> ok.with_json_body(body)
|> Ok()
}
pub fn require_json_body(
req: MRequest(a),
size_limit: Int,
decoder: fn(Dynamic) -> Result(b, List(DecodeError)),
next: fn(b) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Extract the body of the request and attempt to JSON from it with the
given gleam/dynamic
decoder. Returns a 400 if the body is missing or
fails to decode with the given decoder.
Examples
import gleam/dynamic.{type Dynamic, type DecodeErrors}
import messua/handle
import messua/err
import messua/ok
type Book {
Book(
author: String,
title: String,
pub_year: Int,
)
}
fn book_decoder(chunk: Dynamic) -> Result(Book, DecodeErrors) {
chunk
|> dynamic.decode3(
Book,
dynamic.field("author", dynamic.string),
dynamic.field("title", dynamic.string),
dynamic.field("pub_year", dynamic.int)
)
}
fn read_books(req: MRequest) -> MResponse {
use book_list <- handle.require_json_body(
req,
0x1000, // 4 KiB of `Book`s
dynamic.list(book_decoder)
)
case fetch_and_consume_these_books(book_list) {
Ok(_) -> ok.ok() |> Ok() // Okay, okay, okay!!!
Error(_) -> err.new(500)
|> err.with_message(["Couldn't read them all! :^("])
|> Error()
}
}
pub fn require_string_body(
req: MRequest(a),
size_limit: Int,
next: fn(String) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Like require_body
, but returns a String
; will also fail with
a 400 if the body isn’t valid UTF-8.
pub fn require_valid_header(
req: MRequest(a),
name: String,
validator: fn(String) -> Result(b, c),
next: fn(b) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Requires the given header to both exist and have a value acceptable to a supplied validator function. A 400 result with an explanatory message is returned on failure.
Examples
import gleam/int
import gleam/result
import messua/handle
import messua/ok
fn retrieve_user_data(req: MRequest) -> MResponse {
use uid <- handle.require_valid_header(req, "x-user-name", int.parse)
use user <- result.try(
get_user_by_id(uid)
|> result.replace_error(err.new(500))
)
let body = encode_user_details(user)
ok.ok()
|> ok.with_json_body(body)
|> Ok()
}