messua/handle

Helper functions for use in handlers.

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

Reexported type from gleam_http.

pub type Scheme =
  http.Scheme
pub type SockAddr {
  SockAddr(addr: IpAddr, port: Int)
}

Constructors

  • SockAddr(addr: IpAddr, port: Int)

Constants

pub const method_get: Method

Re-exported gleam/http.Method variant.

pub const method_options: Method

Re-exported gleam/http.Method variant.

pub const method_post: Method

Re-exported gleam/http.Method variant.

pub const scheme_http: Scheme

Re-exported gleam/http.Scheme variant.

pub const scheme_https: Scheme

Re-exported gleam/http.Scheme variant.

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

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 host(req: MRequest(a)) -> String
pub fn method(req: MRequest(a)) -> Method
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(req: MRequest(a)) -> String
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_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 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()
}
pub fn scheme(req: MRequest(a)) -> Scheme
Search Document