Current section

Files

Jump to
oaspec src oaspec codegen middleware.gleam
Raw

src/oaspec/codegen/middleware.gleam

import oaspec/codegen/context.{type Context, type GeneratedFile, GeneratedFile}
import oaspec/util/string_extra as se
/// Generate middleware module.
pub fn generate(ctx: Context) -> List(GeneratedFile) {
let content = generate_middleware(ctx)
[
GeneratedFile(
path: "middleware.gleam",
content: content,
target: context.SharedTarget,
),
]
}
/// Generate the middleware types and utilities.
fn generate_middleware(ctx: Context) -> String {
let sb =
se.file_header(context.version)
|> se.imports([])
let sb =
sb
|> se.doc_comment(
"A handler is a function that takes a request and returns a result.",
)
|> se.line("pub type Handler(req, res) =")
|> se.indent(1, "fn(req) -> Result(res, MiddlewareError)")
|> se.blank_line()
let sb =
sb
|> se.doc_comment(
"A middleware wraps a handler to add cross-cutting concerns.",
)
|> se.doc_comment(
"Middleware is composable: each middleware takes a handler and returns",
)
|> se.doc_comment("a new handler with added behavior.")
|> se.line("pub type Middleware(req, res) =")
|> se.indent(1, "fn(Handler(req, res)) -> Handler(req, res)")
|> se.blank_line()
// Error type
let sb =
sb
|> se.doc_comment("Errors that middleware can produce.")
|> se.line("pub type MiddlewareError {")
|> se.indent(1, "Unauthorized(detail: String)")
|> se.indent(1, "Forbidden(detail: String)")
|> se.indent(1, "BadRequest(detail: String)")
|> se.indent(1, "InternalError(detail: String)")
|> se.indent(1, "CustomError(code: Int, detail: String)")
|> se.line("}")
|> se.blank_line()
// Identity middleware
let sb =
sb
|> se.doc_comment(
"Identity middleware that passes the request through unchanged.",
)
|> se.line("pub fn identity() -> Middleware(req, res) {")
|> se.indent(1, "fn(handler: Handler(req, res)) -> Handler(req, res) {")
|> se.indent(2, "handler")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
// Compose middleware
let sb =
sb
|> se.doc_comment("Compose two middleware functions.")
|> se.doc_comment("The first middleware is applied first (outermost),")
|> se.doc_comment("the second middleware is applied second (innermost).")
|> se.line("pub fn compose(")
|> se.indent(1, "first: Middleware(req, res),")
|> se.indent(1, "second: Middleware(req, res),")
|> se.line(") -> Middleware(req, res) {")
|> se.indent(1, "fn(handler: Handler(req, res)) -> Handler(req, res) {")
|> se.indent(2, "first(second(handler))")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
// Apply middleware chain
let sb =
sb
|> se.doc_comment("Apply a list of middleware to a handler.")
|> se.doc_comment(
"Middleware is applied in order: first in list = outermost.",
)
|> se.line("pub fn apply(")
|> se.indent(1, "middlewares: List(Middleware(req, res)),")
|> se.indent(1, "handler: Handler(req, res),")
|> se.line(") -> Handler(req, res) {")
|> se.indent(1, "case middlewares {")
|> se.indent(2, "[] -> handler")
|> se.indent(2, "[mw, ..rest] -> mw(apply(rest, handler))")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
// Logging middleware example
let sb =
sb
|> se.doc_comment("Example: A logging middleware stub.")
|> se.doc_comment("Replace the body with your logging implementation.")
|> se.line("pub fn logging() -> Middleware(req, res) {")
|> se.indent(1, "fn(handler: Handler(req, res)) -> Handler(req, res) {")
|> se.indent(2, "fn(request: req) -> Result(res, MiddlewareError) {")
|> se.indent(3, "// TODO: Add logging before/after the handler call")
|> se.indent(3, "handler(request)")
|> se.indent(2, "}")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
// Retry middleware for client
let sb =
sb
|> se.doc_comment("Retry middleware for client operations.")
|> se.doc_comment("Retries the handler up to max_retries times on failure.")
|> se.line("pub fn retry(max_retries: Int) -> Middleware(req, res) {")
|> se.indent(1, "fn(handler: Handler(req, res)) -> Handler(req, res) {")
|> se.indent(2, "fn(request: req) -> Result(res, MiddlewareError) {")
|> se.indent(3, "do_retry(handler, request, max_retries, 0)")
|> se.indent(2, "}")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
let sb =
sb
|> se.line("fn do_retry(")
|> se.indent(1, "handler: Handler(req, res),")
|> se.indent(1, "request: req,")
|> se.indent(1, "max_retries: Int,")
|> se.indent(1, "attempt: Int,")
|> se.line(") -> Result(res, MiddlewareError) {")
|> se.indent(1, "case handler(request) {")
|> se.indent(2, "Ok(response) -> Ok(response)")
|> se.indent(2, "Error(err) -> {")
|> se.indent(3, "case attempt < max_retries {")
|> se.indent(
4,
"True -> do_retry(handler, request, max_retries, attempt + 1)",
)
|> se.indent(4, "False -> Error(err)")
|> se.indent(3, "}")
|> se.indent(2, "}")
|> se.indent(1, "}")
|> se.line("}")
|> se.blank_line()
let _ = ctx
se.to_string(sb)
}