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.

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
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)
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)
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_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