Current section
Files
Jump to
Current section
Files
src/bliss.gleam
import gleam/bit_builder.{BitBuilder}
import gleam/http
import gleam/http/request
import gleam/http/response.{Response}
import gleam/list
import gleam/json
pub const header_content_type = "Content-Type"
/// A Request
pub type WebRequest =
request.Request(BitString)
/// Standard response errors
/// Use Unmatched to signal that the current path hasn't match
pub type ResponseError {
Unmatched
NotFound
Unauthorised
}
/// Expected response by handlers
pub type WebResponse =
Result(Response(BitBuilder), ResponseError)
/// Everything is a handler in Bliss
/// A handler is a function that takes a WebRequest and a context and returns a WebResponse
/// A context is your own type, which could hold any data e.g. current_user, db_pool
pub type Handler(ctx) =
fn(WebRequest, ctx) -> WebResponse
/// A middleware is a function that wraps a handler
/// Middlewares can change the request in the way in
/// and the response in the way out
pub type Middleware(ctx_in, ctx_out) =
fn(Handler(ctx_out)) -> Handler(ctx_in)
/// Take the request and context and return a handler to call
/// Use this for routing
///
/// ```
/// bliss.user_hander(fn(req, ctx) {
/// case request.path_segments(req) {
/// ["api", ..rest] -> api_routes(rest)
/// rest -> public_routes(rest)
/// }
/// })
/// ```
pub fn use_handler(provide: fn(WebRequest, ctx) -> Handler(ctx)) -> Handler(ctx) {
fn(req: WebRequest, ctx) {
let handler = provide(req, ctx)
handler(req, ctx)
}
}
fn is_wanted_method(wanted_method: http.Method, req: WebRequest) {
case wanted_method {
http.Other("*") -> True
_ -> req.method == wanted_method
}
}
fn has_wanted_method(wanted_methods: List(http.Method), req: WebRequest) {
list.any(wanted_methods, is_wanted_method(_, req))
}
/// *********************
/// Basic Middlewares
/// *********************
///
/// A middleware that only calls the wrapped handler
/// if the request uses the allowed methods
///
/// ```
/// ["users", id] -> delete_user(id) |> bliss.if_methods([Delete])
/// ```
///
pub fn if_methods(wanted_methods: List(http.Method)) -> Middleware(ctx, ctx) {
fn(handler: Handler(ctx)) -> Handler(ctx) {
fn(req: WebRequest, ctx) {
let has_wanted = has_wanted_method(wanted_methods, req)
case has_wanted {
True -> handler(req, ctx)
False -> Error(Unmatched)
}
}
}
}
/// A middleware that only calls the wrapped handler
/// if the request uses Get
pub fn if_get(handler: Handler(ctx)) -> Handler(ctx) {
if_methods([http.Get])(handler)
}
/// A middleware that only calls the wrapped handler
/// if the request uses Post
pub fn if_post(handler: Handler(ctx)) -> Handler(ctx) {
if_methods([http.Post])(handler)
}
/// A middleware that only calls the wrapped handler
/// if the request uses Delete
pub fn if_delete(handler: Handler(ctx)) -> Handler(ctx) {
if_methods([http.Delete])(handler)
}
/// *********************
/// Basic handlers
/// *********************
///
/// A handler that always responds with not found
pub fn not_found(_req: WebRequest, _ctx) -> WebResponse {
Ok(response_not_found())
}
/// A handler that returns Unmatched
/// Use the to indicate that the framework should keep
/// trying to match a route
///
/// ```
/// case path {
/// ["users"] -> ...
/// _ -> bliss.unmatched
/// }
/// ```
///
pub fn unmatched(_req: WebRequest, _ctx) -> WebResponse {
Error(Unmatched)
}
/// *********************
/// Common responses
/// *********************
///
/// Create a JSON response
pub fn json_response(data: json.Json) -> Response(BitBuilder) {
let body =
data
|> json.to_string
|> bit_builder.from_string
response.new(200)
|> response.set_body(body)
|> response.prepend_header(header_content_type, "application/json")
}
fn response_not_found() {
let body = bit_builder.from_string("Not Found")
response.new(404)
|> response.set_body(body)
}
fn response_unauthorised() {
response.new(401)
|> response.set_body(bit_builder.from_string("Unauthorized"))
}
fn response_for_error(error: ResponseError) {
case error {
NotFound -> response_not_found()
Unauthorised -> response_unauthorised()
Unmatched -> response_not_found()
}
}
/// Create the web service
/// Pass this to a web server
pub fn service(handler: Handler(context), context context: context) {
fn(request: request.Request(BitString)) {
case handler(request, context) {
Ok(resp) -> resp
Error(error) -> response_for_error(error)
}
}
}