howdy/service

Services hold application logic and return plain results. This module turns those results into HTTP responses so controllers stay lean.

fn by_id(ctx: Context) {
  use id <- param.int(ctx, "id")
  user_service.find(id)
  |> service.respond(ctx, user.to_json)
}

Types

The ways a service call can fail. Each variant maps to a status code.

pub type Error {
  NotFound(String)
  Invalid(String)
  Conflict(String)
  Unauthorized
  Forbidden
  UnsupportedMediaType(String)
  Internal(String)
  Validation(List(FieldError))
  TooManyRequests(retry_after_seconds: Int)
  Coded(code: String, error: Error)
}

Constructors

  • NotFound(String)
    1. The message names what was not found.
  • Invalid(String)
    1. The message explains what was wrong with the input.
  • Conflict(String)
    1. The message explains the conflict.
  • Unauthorized
  • Forbidden
  • UnsupportedMediaType(String)
    1. The message names the content type that was expected. Usually produced by howdy/form.
  • Internal(String)
    1. The message is logged and never sent to the client.
  • Validation(List(FieldError))
    1. Input was well formed but failed validation. Usually produced by howdy/validate, but a service can return it too, for example for a duplicate email.
  • TooManyRequests(retry_after_seconds: Int)

    429 with a retry-after header. Usually produced by howdy/rate_limit, but a service can return it too, for example for password resets.

  • Coded(code: String, error: Error)

    Another error with a stable code for clients, which answers as error does with code in its body. A client that shows errors in other languages translates by code, since the message is English: Coded("email_taken", Conflict("an account already exists")). uncoded gives the error inside, for matching on its kind.

One problem with one field. The same type as validate.FieldError.

pub type FieldError {
  FieldError(field: String, problem: problem.Problem)
}

Constructors

The result type every service function returns.

pub type Result(a) =
  Result(a, Error)

Values

pub fn code(error: Error) -> String

The code sent to the client for an error: a Coded error’s own, or one for its kind, such as not_found or too_many_requests.

pub fn created(
  result: Result(a, Error),
  ctx: context.Context(guarded),
  encode: fn(a) -> json.Json,
) -> response.Response(content.Content)

Like respond but Ok becomes 201.

pub fn error_response(
  ctx: context.Context(guarded),
  error: Error,
) -> response.Response(content.Content)

Build the response for an error. Used by respond and by the extractors in howdy/param and howdy/body so every error has the same shape.

pub fn message(error: Error) -> String

The message sent to the client for an error.

pub fn no_content(
  result: Result(Nil, Error),
  ctx: context.Context(guarded),
) -> response.Response(content.Content)

Ok becomes 204 with no body. Useful for deletes.

pub fn respond(
  result: Result(a, Error),
  ctx: context.Context(guarded),
  encode: fn(a) -> json.Json,
) -> response.Response(content.Content)

Ok becomes 200 with the encoded value. Error becomes the matching status with a JSON body of {"error": "...", "code": "..."}.

pub fn status_code(error: Error) -> Int

The status code an error maps to.

pub fn uncoded(error: Error) -> Error

The error without any Coded around it.

✨ Search Document