Current section

Files

Jump to
glitr_wisp src glitr wisp.gleam
Raw

src/glitr/wisp.gleam

import gleam/bool
import gleam/list
import gleam/result
import gleam/string_builder
import glitr
import glitr/body
import glitr/path
import glitr/query
import glitr/route
import glitr/wisp/errors
import wisp
/// A Router is basically a request handler for glitr Routes
/// It store the incoming request and will compare it to the different Routes that are provided to it.
/// The resulting response is generated based on the matching Route and the handler associated to it.
pub opaque type Router {
Router(req: wisp.Request)
}
/// Create a new Router for the incoming request
pub fn for(req: wisp.Request) -> Result(Router, wisp.Response) {
Ok(Router(req))
}
/// Compare the stored request with the provided Route
/// If the first parameter has a response, this will be ignored.
/// Otherwise, if the request match the Route, generate a response based on the provided handler,
/// else return the Router so that these functions can be chained.
pub fn try(
router_res: Result(Router, wisp.Response),
route: route.Route(p, q, b, res),
handler: fn(glitr.RouteOptions(p, q, b)) -> Result(res, errors.AppError),
) -> Result(Router, wisp.Response) {
use router <- result.try(router_res)
let result = receive(router.req, route, handler)
case result {
Ok(response) -> Error(response)
Error(Nil) -> Ok(router)
}
}
/// Do the same as the try function but also map the generated response
pub fn try_map(
router_res: Result(Router, wisp.Response),
route: route.Route(p, q, b, res),
handler: fn(glitr.RouteOptions(p, q, b)) -> Result(res, errors.AppError),
map_fn: fn(wisp.Response) -> wisp.Response,
) -> Result(Router, wisp.Response) {
use router <- result.try(router_res)
let result = receive(router.req, route, handler)
case result {
Ok(response) -> Error(response |> map_fn)
Error(Nil) -> Ok(router)
}
}
/// Unwrap the response generated by a Route or return a not_found if no route matched
pub fn unwrap(router_res: Result(Router, wisp.Response)) -> wisp.Response {
case router_res {
Ok(_) -> wisp.not_found()
Error(response) -> response
}
}
/// Test if the request match the Route, if so extract the path, query and body data and execute the handler function
/// to generate a response
fn receive(
req: wisp.Request,
route: route.Route(p, q, b, res),
handler: fn(glitr.RouteOptions(p, q, b)) -> Result(res, errors.AppError),
) -> Result(wisp.Response, Nil) {
use <- bool.guard(req.method != route.method, Error(Nil))
use path <- handle_path(req, route)
use query <- handle_query(req, route)
use body <- handle_body(req, route)
use <- handle_result(route)
handler(glitr.RouteOptions(path, query, body))
}
/// Try extracting the path data from a request given a Route
/// If successful, call callback with it.
/// Otherwise, return an error
fn handle_path(
req: wisp.Request,
route: route.Route(p, _, _, _),
callback: fn(p) -> Result(wisp.Response, Nil),
) -> Result(wisp.Response, Nil) {
let path_result = req |> wisp.path_segments |> path.decode(route.path, _)
case path_result {
Ok(path) -> callback(path)
Error(_) -> Error(Nil)
}
}
/// Try extracting the query data from a request given a Route
/// If successful, call callback with it.
/// Otherwise, return an error
fn handle_query(
req: wisp.Request,
route: route.Route(_, q, _, _),
callback: fn(q) -> Result(wisp.Response, Nil),
) -> Result(wisp.Response, Nil) {
let path_result = req |> wisp.get_query |> query.decode(route.query, _)
case path_result {
Ok(path) -> callback(path)
Error(_) -> Error(Nil)
}
}
/// Try extracting the body data from a request given a Route
/// If successful, call callback with it.
/// Otherwise, return an error
fn handle_body(
req: wisp.Request,
route: route.Route(_, _, b, _),
callback: fn(b) -> wisp.Response,
) -> Result(wisp.Response, Nil) {
let call_callback = fn(res) {
case res {
Ok(value) -> callback(value)
Error(_) ->
wisp.bad_request()
|> wisp.string_body("Error while decoding body as JSON")
}
}
let body = case route.req_body |> body.get_type {
body.EmptyBody -> route.req_body |> body.decode("") |> call_callback
body.JsonBody -> {
use value <- wisp.require_string_body(req)
use <- bool.guard(
req.headers |> list.key_find("content-type") != Ok("application/json"),
wisp.unsupported_media_type(["application/json"]),
)
route.req_body
|> body.decode(value)
|> call_callback
}
body.StringBody -> {
use value <- wisp.require_string_body(req)
route.req_body
|> body.decode(value)
|> call_callback
}
}
Ok(body)
}
/// Generate a response with the correct data if handler is successful,
/// or an error response otherwise
fn handle_result(
route: route.Route(_, _, _, res),
handler: fn() -> Result(res, errors.AppError),
) -> wisp.Response {
let result = handler()
case result {
Error(errors.DecoderError(msg)) ->
wisp.bad_request()
|> wisp.set_body(wisp.Text(string_builder.from_string(msg)))
Error(errors.DBError(msg)) ->
wisp.internal_server_error()
|> wisp.set_body(wisp.Text(string_builder.from_string(msg)))
Error(errors.InternalError(msg)) ->
wisp.internal_server_error()
|> wisp.set_body(wisp.Text(string_builder.from_string(msg)))
Ok(res) ->
case route.res_body |> body.get_type {
body.JsonBody ->
route.res_body
|> body.encode(res)
|> wisp.json_response(200)
body.StringBody ->
wisp.ok()
|> wisp.set_body(wisp.Text(
route.res_body
|> body.encode(res),
))
body.EmptyBody -> wisp.ok()
}
}
}