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)
Reexported type from gleam_http
.
method_get
, method_post
, and
method_options
are exposed in this namespace as
constants. To check for other methods, you’ll need to import
gleam/http
.
pub type Method =
http.Method
Values
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
Format a SockAddr
like you’d expect:
SockAddr(addr: Ipv4(192, 168, 1, 1), port: 8080)
|> handle.fmt_sock_addr()
// 192.168.1.1:8080
SockAddr(addr: Ipv6(10, 1534, 2020, 92, 7, 0, 0, 9), port: 443)
|> handle.fmt_sock_addr()
|> io.println()
// [000A:05FE:07E4:005C:0007:0000:0000:0009]:443
pub fn get_body(
req: MRequest(a),
size_limit: Int,
) -> Option(BitArray)
Return the body of the request if there is one, and it’s less than
size_limit
bytes long.
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 get_query(req: MRequest(a)) -> Option(String)
Return the request URL’s query string (if there is one).
pub fn get_string_body(
req: MRequest(a),
size_limit: Int,
) -> Option(String)
Return the body of the request if there is one, it’s less than
size_limit
bytes long, and is valid UTF-8.
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 parse_ip(str: String) -> Result(IpAddr, Nil)
Attempt to parse an IPV4 or IPV6 address from a string.
handle.parse_ip("127.0.0.1")
|> string.inspect()
|> io.println()
// Ok(Ipv4(127, 0, 0, 1))
handle.parse_ip("32ff:40:0b0::3456")
|> string.inspect()
|> io.println()
// Ok(Ipv6(13055, 64, 176, 0, 0, 0, 0, 13398))
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()
}
~/ $ curl localhost:8080
request requires a "x-user-name" header
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 decode 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_query(
req: MRequest(a),
next: fn(String) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
Return the request URL’s query string, or answer with a 400 response.
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 parse_uid(uid: String) -> Result(Int, String) {
int.parse(uid)
|> result.replace_error("must be a valid integer")
}
fn retrieve_user_data(req: MRequest) -> MResponse {
use uid <- handle.require_valid_header(req, "x-user-id", parse_uid)
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()
}
~/ $ curl localhost:8080
request requires a "x-user-id" header
›
~/ $ curl localhost:8080 -H "x-user-id: foo"
"foo" is not a valid x-user-id value: "must be a valid integer"
pub fn require_valid_optional_header(
req: MRequest(a),
name: String,
validator: fn(String) -> Result(b, c),
next: fn(Option(b)) -> Result(Response(ResponseData), Err),
) -> Result(Response(ResponseData), Err)
If the given header exists, requires it to be acceptable to the supplied
validator
function, or it returns a 400 with an explanatory message.
If the header isn’t present, it continues with a None
value.