crooner
| Kind | kit |
|---|---|
| Categories | web network |
| Keywords | web framework http server routing sinatra |
A Sinatra-inspired web framework for Kit
Files
| File | Description |
|---|---|
kit.toml | Package manifest with metadata and dependencies |
src/components.kit | HTML component helpers and template rendering |
src/main.kit | Main module - exports all public functions and types |
src/request.kit | HTTP request parsing and headers |
src/response.kit | HTTP response building and status codes |
src/router.kit | URL routing with pattern matching and filters |
src/server.kit | TCP server and HTTP lifecycle management |
tests/components.test.kit | Tests for HTML component generation |
tests/crooner.test.kit | Core framework integration tests |
examples/components-demo.kit | HTML component and template usage |
examples/csrf-protection.kit | CSRF token validation middleware |
examples/filters.kit | Before/after filter pipeline demo |
examples/hello.kit | Minimal "Hello World" web server |
examples/http.kit | HTTP client request examples |
examples/middleware.kit | Custom middleware chain setup |
examples/simple-server.kit | Basic HTTP server with routes |
examples/static-files.kit | Serving static files from disk |
examples/tcp.kit | Low-level TCP socket handling |
examples/welcome-test.kit | Welcome page response test |
LICENSE | MIT license file |
Architecture
Request Lifecycle
Module Structure
Dependencies
loggingtemplate
Installation
kit add gitlab.com/kit-lang/packages/kit-crooner.gitUsage
import Kit.CroonerLicense
MIT License - see LICENSE for details.
Exported Functions & Types
CroonerError
Crooner error type for typed error handling. Variants distinguish between different failure modes.
Variants
CroonerServerError {message}CroonerConnectionError {message}CroonerRouteError {message}CroonerRequestError {message}app
Create a Crooner application with default configuration.
Returns a new application record with empty routes, error handlers, and filters, and a default compact-format logger.
() -> App
app = Crooner.app
|> Crooner.get "/" fn(req) => {body: "Hello!"}app-with-options
Create a Crooner application with custom logger configuration.
Allows customization of the logger format and request event logging. Supported formats are from the Logging module (CompactFmt, JsonFmt, etc.).
{logger-format: LogFormat, request-events: Bool} -> App
# Enable request events for detailed request logging
app = Crooner.app-with-options {request-events: true}
# Combine with JSON format for production
app = Crooner.app-with-options {logger-format: Logging.JsonFmt, request-events: true}with-logger
Set or replace the logger on an existing app.
Useful for integrating a custom logger or changing logging configuration after app creation.
App -> Logger -> App
custom-logger = Logging.logger my-config
app = Crooner.app |> Crooner.with-logger custom-loggerwith-request-events
Enable request-scoped event logging.
When enabled, each request gets a Logging event attached to req.event that accumulates context throughout the request lifecycle. Handlers and filters can add fields to the event, and a single comprehensive log entry is emitted after the response is sent.
App -> App
app = Crooner.app |> Crooner.with-request-events
# In your handler, access the event:
handler = fn(req) =>
req.event |> Logging.add-field "user-id" 42
{status: 200, body: "OK"}with-event-format
Set the format for request event logging.
Controls how request events are formatted when emitted. Use this to match the event format to your application's logging style.
App -> LogFormat -> App
app = Crooner.app
|> Crooner.with-logger my-logger
|> Crooner.with-event-format Logging.PrettyFmt
|> Crooner.with-request-eventsget
Register a GET route handler.
Registers a handler function for HTTP GET requests matching the pattern. Patterns can include path parameters using :name syntax.
App -> String -> (Request -> Response) -> App
app |> Crooner.get "/users/:id" fn(req) =>
id = Crooner.get-param req "id" |> Option.unwrap "0"
{body: "User ${id}"}route-get
Register a GET route handler (alias for get).
Use this when get conflicts with other imports (e.g., HTTP.get). Functionally identical to Crooner.get.
App -> String -> (Request -> Response) -> App
post
Register a POST route handler.
App -> String -> (Request -> Response) -> App
put
Register a PUT route handler.
App -> String -> (Request -> Response) -> App
delete
Register a DELETE route handler.
App -> String -> (Request -> Response) -> App
patch
Register a PATCH route handler.
App -> String -> (Request -> Response) -> App
head-request
Register a HEAD route handler.
App -> String -> (Request -> Response) -> App
options
Register an OPTIONS route handler.
App -> String -> (Request -> Response) -> App
error
Register a custom error handler for a specific status code.
Allows customization of error responses for specific HTTP status codes.
App -> Int -> (Request -> Response) -> App
app |> Crooner.error 404 fn(req) =>
{status: 404, body: "Page not found: ${req.path}"}before
Register a before filter that runs before route handlers.
Before filters can modify the request or short-circuit by returning a response. They execute in registration order.
App -> (Request -> Request | Response) -> App
auth-filter = fn(req) =>
match Crooner.get-header req "Authorization"
| Some _ -> req # Continue with request
| None -> {status: 401, body: "Unauthorized"} # Short-circuit
app |> Crooner.before auth-filterafter
Register an after filter that runs after route handlers.
After filters can modify the response before it's sent to the client. They execute in registration order and receive both request and response.
App -> (Request -> Response -> Response) -> App
cors-filter = fn(req, resp) =>
# Add CORS headers to response
{status: resp.status, body: resp.body,
headers: Map.insert "Access-Control-Allow-Origin" "*" resp.headers}
app |> Crooner.after cors-filterstatic
Register a static file serving route for a URL prefix and filesystem path.
Maps a URL prefix to a filesystem directory for serving static files. Automatically sets appropriate Content-Type headers based on file extensions.
App -> String -> String -> App
app |> Crooner.static "/assets" "./public/assets"
# Now /assets/style.css serves ./public/assets/style.cssuse
Add Ring-style middleware that wraps the request/response cycle.
Ring-style middleware is a higher-order function: (handler -> handler). Middleware forms a chain where each wraps the next, creating an "onion" model. First middleware added is the outermost layer.
App -> ((Request -> Response) -> (Request -> Response)) -> App
wrap-logging = fn(handler) =>
fn(req) =>
println "Before: ${req.path}"
resp = handler req
println "After: ${resp.status}"
resp
app |> Crooner.use wrap-loggingcsrf-protect
Add CSRF protection using Sec-Fetch-Site header validation.
Uses the Fetch Metadata standard (Sec-Fetch-Site header) to validate requests. Only state-changing methods (POST, PUT, DELETE, PATCH) are checked. Safe values are: "same-origin", "same-site", "none".
App -> {exclude-paths: List String} -> App
app |> Crooner.csrf-protect {exclude-paths: ["/api/webhook"]}csrf-protect-simple
Add simple CSRF protection with no exclusions.
Convenience function for adding CSRF protection without any excluded paths.
App -> App
app |> Crooner.csrf-protect-simplelisten
Start the server on all interfaces (0.0.0.0).
Binds to 0.0.0.0, making the server accessible from any network interface. Does not return (enters server loop).
App -> Int -> (() -> a) -> ()
Crooner.listen app 3000 fn =>
println "Server started on http://0.0.0.0:3000"listen-local
Start the server on localhost only (127.0.0.1).
Binds to 127.0.0.1, making the server only accessible from the local machine. Useful for development or when running behind a reverse proxy. Does not return (enters server loop).
App -> Int -> (() -> a) -> ()
Crooner.listen-local app 3000 fn =>
println "Server started on http://127.0.0.1:3000"ok
Create a 200 OK response with the given body.
String -> Response
created
Create a 201 Created response with the given body.
String -> Response
no-content
Create a 204 No Content response.
() -> Response
bad-request
Create a 400 Bad Request response with the given body.
String -> Response
not-found
Create a 404 Not Found response with the given body.
String -> Response
internal-error
Create a 500 Internal Server Error response with the given body.
String -> Response
redirect
Create a 302 redirect response to the given URL.
String -> Response
redirect-permanent
Create a 301 permanent redirect response to the given URL.
String -> Response
json-response
Create a JSON response with the given status and body. Sets Content-Type to application/json; charset=utf-8.
Int -> String -> Response
html-response
Create an HTML response with the given status and body. Sets Content-Type to text/html; charset=utf-8.
Int -> String -> Response
text-response
Create a plain text response with the given status and body. Sets Content-Type to text/plain; charset=utf-8.
Int -> String -> Response
get-header
Get a header value from a request. Header lookup is case-sensitive as stored in the request.
Request -> String -> Option String
get-query
Get a query parameter value from a request. Extracts a value from the URL query string (e.g., ?key=value).
Request -> String -> Option String
get-query-param
Alias for get-query (backwards compatibility).
Request -> String -> Option String
get-param
Get a path parameter value from a request. Extracts a value from a path pattern parameter (e.g., :id in /users/:id).
Request -> String -> Option String
# Route: /users/:id
# Request: /users/42
id = Crooner.get-param req "id" # Some "42"get-event
Get the logging event from a request. When request-events are enabled, returns the event that can be enriched with additional context throughout the request lifecycle.
Request -> Option Event
handler = fn(req) =>
evt = Crooner.get-event req |> Option.unwrap
evt |> Logging.add-field "user-id" 42
evt |> Logging.add-timing "db-query-ms" 15
{status: 200, body: "OK"}has-event?
Check if a request has a logging event attached. Returns true if request has an event (request-events enabled), false otherwise.
Request -> Bool
default-config
Get the default server configuration.
Returns: Default configuration with host "0.0.0.0" and port 3000.
Config
config = default-config
# {host: "0.0.0.0", port: 3000}handle-request
Handle a single HTTP request by routing and sending the response.
Takes the parsed HTTP request from HTTP.server-accept, routes it through the router, and sends back the HTTP response via HTTP.server-respond.
When request-events are enabled, creates a logging event at the start of the request, attaches it to the request object, and emits a comprehensive log entry after the response is sent.
Parameters:
router - Router record with routes and handlershttp-request - Parsed HTTP request from HTTP.server-accept
Returns: Unit (connection is closed after responding)
Router -> HttpRequest -> Unit
handle-request router http-requestaccept-loop
Accept and handle HTTP requests in a loop.
Continuously accepts incoming HTTP requests and handles each one. This is a blocking loop that runs until the server is stopped.
Parameters:
router - Router record with routes and handlersserver-handle - HTTP server handle from HTTP.server-create
Returns: Never returns (infinite loop)
Router -> Ptr -> Unit
accept-loop router server-handlestart
Start the server with the given configuration.
Creates an HTTP server and starts accepting client connections. Calls the on-start callback after the server is created successfully.
Parameters:
router - Router record with routes and handlersconfig - Configuration record with host and port fieldson-start - Callback function to run after server starts
Returns: Never returns (enters accept loop)
Router -> Config -> (() -> ()) -> Unit
config = {host: "127.0.0.1", port: 8080}
start router config fn =>
println "Server started!"listen
Start the server on all interfaces (0.0.0.0).
Convenience function that starts the server listening on all network interfaces.
Parameters:
router - Router record with routes and handlersport - Port number to listen onon-start - Callback function to run after server starts
Returns: Never returns (enters accept loop)
Router -> Int -> (() -> ()) -> Unit
listen router 3000 fn =>
println "Listening on port 3000"listen-local
Start the server on localhost only (127.0.0.1).
Convenience function that starts the server listening only on localhost. Useful for development or when the server should only accept local connections.
Parameters:
router - Router record with routes and handlersport - Port number to listen onon-start - Callback function to run after server starts
Returns: Never returns (enters accept loop)
Router -> Int -> (() -> ()) -> Unit
listen-local router 3000 fn =>
println "Listening on localhost:3000"status-text
Get the standard text description for an HTTP status code.
Parameters:
code - HTTP status code (e.g., 200, 404, 500)
Returns: Standard status text for the code (e.g., "OK", "Not Found")Returns "Unknown" for unrecognized codes.
Int -> String
text = status-text 404 # "Not Found"response
Create a basic response with status and body.
Creates a plain text response with the given status code and body. Default content-type is "text/plain; charset=utf-8".
Parameters:
status - HTTP status codebody - Response body string
Returns: Response record with kind, status, headers, and body fields.
Int -> String -> Response
resp = response 200 "Success"from-handler-result
Convert a handler result (string or record) into a normalized response.
Normalizes different handler return values into a standard response record. Accepts strings, partial response records, or full response records.
Parameters:
result - Handler return value (string or record)
Returns: Normalized Response record with all required fields.
a -> Response
# String result
resp = from-handler-result "Hello" # 200 OK with body "Hello"
# Partial record
resp = from-handler-result {body: "OK"} # 200 OK with body "OK"
# Full record
resp = from-handler-result {status: 201, body: "Created", headers: {}}build
Build the raw HTTP response string from a response record.
Converts a response record into a raw HTTP response string suitable for sending over TCP. Automatically adds Content-Length header based on body size.
Parameters:
resp - Response record with status, body, and headers
Returns: Raw HTTP response string with status line, headers, and body.
Response -> String
raw = build {kind: "response", status: 200, body: "OK", headers: {}}
# "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"to-http-response
Convert a Crooner response to HTTP server response format.
Converts a Crooner response record (with headers as a Record) into the format expected by HTTP.server-respond (with headers as a list of records).
Parameters:
resp - Crooner response record with kind, status, headers (Record), and body
Returns: HTTP server response record with status, body, and headers as [{name, value}]
Response -> HttpResponse
crooner-resp = ok "Hello"
http-resp = to-http-response crooner-resp
HTTP.server-respond handle http-respok
Create a 200 OK response.
Parameters:
body - Response body string
Returns: Response with status 200
String -> Response
created
Create a 201 Created response.
Parameters:
body - Response body string
Returns: Response with status 201
String -> Response
no-content
Create a 204 No Content response.
Returns: Response with status 204 and empty body
Unit -> Response
bad-request
Create a 400 Bad Request response.
Parameters:
body - Response body string
Returns: Response with status 400
String -> Response
unauthorized
Create a 401 Unauthorized response.
Parameters:
body - Response body string
Returns: Response with status 401
String -> Response
forbidden
Create a 403 Forbidden response.
Parameters:
body - Response body string
Returns: Response with status 403
String -> Response
not-found
Create a 404 Not Found response.
Parameters:
body - Response body string
Returns: Response with status 404
String -> Response
method-not-allowed
Create a 405 Method Not Allowed response.
Parameters:
body - Response body string
Returns: Response with status 405
String -> Response
internal-error
Create a 500 Internal Server Error response.
Parameters:
body - Response body string
Returns: Response with status 500
String -> Response
redirect
Create a 302 temporary redirect response.
Parameters:
url - URL to redirect to
Returns: Response with status 302 and Location header
String -> Response
resp = redirect "/login"redirect-permanent
Create a 301 permanent redirect response.
Parameters:
url - URL to redirect to
Returns: Response with status 301 and Location header
String -> Response
resp = redirect-permanent "/new-location"json-response
Create a JSON response with the given status and body.
Parameters:
status - HTTP status codebody - JSON string
Returns: Response with application/json content-type
Int -> String -> Response
resp = json-response 200 "{\"status\":\"ok\"}"html-response
Create an HTML response with the given status and body.
Parameters:
status - HTTP status codebody - HTML string
Returns: Response with text/html content-type
Int -> String -> Response
resp = html-response 200 "<h1>Hello</h1>"text-response
Create a plain text response with the given status and body.
Parameters:
status - HTTP status codebody - Plain text string
Returns: Response with text/plain content-type
Int -> String -> Response
resp = text-response 200 "Hello, World!"empty-request
Create an empty request record.
Returns: An empty Request record with default values for all fields.
Unit -> Request
req = empty-request
# req.method == ""
# req.path == ""from-http-request
Convert an HTTP server request to a Crooner request record.
This function is used internally by the server to convert the raw HTTP server request format from HTTP.server-accept into Crooner's structured request format.
Parameters:
http-request - Request from HTTP.server-accept with format-{method, path, headers- [{name, value}], body, handle}
Returns: Crooner request record with headers as Map and query params parsed
HttpRequest -> Request
http-req = HTTP.server-accept server-handle
req = from-http-request http-reqparse-request
Parse a raw HTTP request string into a request record.
Parses the full HTTP request including request line, headers, and body. Automatically extracts query parameters from the URL and separates the request body from headers.
Parameters:
raw - Raw HTTP request string with CRLF line endings
Returns: Request record with parsed method, path, version, headers, query, and body.Returns empty-request if parsing fails.
String -> Request
raw = "POST /api/users HTTP/1.1\r\nContent-Type: application/json\r\n\r\n{\"name\":\"Alice\"}"
req = parse-request raw
# req.method == "POST"
# req.path == "/api/users"
# req.body == "{\"name\":\"Alice\"}"get-header
Get a header value from the request by name.
Parameters:
req - Request recordname - Header name (case-sensitive)
Returns: Some value if header exists, None otherwise
Request -> String -> Option String
content-type = get-header req "Content-Type"get-query
Get a query parameter value from the request by name.
Parameters:
req - Request recordname - Query parameter name
Returns: Some value if query parameter exists, None otherwise
Request -> String -> Option String
filter = get-query req "filter"get-param
Get a path parameter value from the request by name.
Path parameters are extracted from URL patterns like "/users/:id".
Parameters:
req - Request recordname - Path parameter name (without the colon prefix)
Returns: Some value if path parameter exists, None otherwise
Request -> String -> Option String
# For route "/users/:id" and path "/users/123"
user-id = get-param req "id" # Some "123"is-method?
Check if the request method matches the given method.
Parameters:
req - Request recordmethod - HTTP method to check (e.g., "GET", "POST")
Returns: true if request method matches, false otherwise
Request -> String -> Bool
if is-method? req "POST" then
# Handle POST requestis-get?
Check if the request method is GET.
Parameters:
req - Request record
Returns: true if method is GET, false otherwise
Request -> Bool
is-post?
Check if the request method is POST.
Parameters:
req - Request record
Returns: true if method is POST, false otherwise
Request -> Bool
is-put?
Check if the request method is PUT.
Parameters:
req - Request record
Returns: true if method is PUT, false otherwise
Request -> Bool
is-delete?
Check if the request method is DELETE.
Parameters:
req - Request record
Returns: true if method is DELETE, false otherwise
Request -> Bool
is-patch?
Check if the request method is PATCH.
Parameters:
req - Request record
Returns: true if method is PATCH, false otherwise
Request -> Bool
attach-event
Attach a logging event to a request.
Used internally by the server when request-events are enabled. The event accumulates context throughout the request lifecycle.
Parameters:
req - Request recordevent - Logging event from Logging.event
Returns: Request record with event attached
Request -> Event -> Request
req = attach-event req eventget-event
Get the logging event from a request.
Returns the event if request-events are enabled, None otherwise.
Parameters:
req - Request record
Returns: Option Event - Some event if attached, None otherwise
Request -> Option Event
match get-event req
| Some evt -> evt |> Logging.add-field "key" "value"
| None ->has-event?
Check if a request has an event attached.
Parameters:
req - Request record
Returns: true if request has an event, false otherwise
Request -> Bool
router
Create an empty router with no routes or handlers.
Returns: Empty Router record with empty routes, error-handlers, filters, and middleware lists.
Router
app = routerhas-routes?
Check if the router has any routes defined.
Parameters:
router - Router record
Returns: true if router has at least one route, false otherwise
Router -> Bool
if has-routes? router then
# Router has routeswelcome-page
Generate the default welcome page response.
Returns: HTML response with welcome page, shown when no routes are defined.Includes current Kit and Crooner version numbers.
Response
resp = welcome-pageadd-route
Add a route to the router with method, pattern, and handler.
Parameters:
router - Router recordmethod - HTTP method (e.g., "GET", "POST")pattern - URL pattern with optional path parameters (e.g., "/users/- id")handler - Handler function that takes a request and returns a response
Returns: Updated Router with the new route added.
Router -> String -> String -> (Request -> Response) -> Router
router = add-route router "GET" "/users/:id" fn(req) =>
{status: 200, body: "User page"}add-error-handler
Register a custom error handler for a specific HTTP status code.
Parameters:
router - Router recordstatus-code - HTTP status code (e.g., 404, 500)handler - Handler function that takes a request and returns a response
Returns: Updated Router with the custom error handler registered.
Router -> Int -> (Request -> Response) -> Router
router = add-error-handler router 404 fn(req) =>
{status: 404, body: "Custom not found page"}add-before-filter
Add a before filter that runs before route handlers.
Before filters can modify requests or short-circuit request processing by returning a response instead of the (possibly modified) request.
Parameters:
router - Router recordfilter - Filter function that takes a request and returns a request or response
Returns: Updated Router with the before filter added.
Router -> (Request -> Request | Response) -> Router
auth-filter = fn(req) =>
match get-header req "Authorization"
| Some token -> req # Continue processing
| None -> {status: 401, body: "Unauthorized"} # Short-circuit
router = add-before-filter router auth-filteradd-after-filter
Add an after filter that runs after route handlers.
After filters can modify responses before they are sent to the client.
Parameters:
router - Router recordfilter - Filter function that takes (request, response) and returns a response
Returns: Updated Router with the after filter added.
Router -> (Request -> Response -> Response) -> Router
cors-filter = fn(req, resp) =>
# Add CORS headers to response
{kind: "response", status: resp.status, body: resp.body,
headers: Map.insert "Access-Control-Allow-Origin" "*" resp.headers}
router = add-after-filter router cors-filteradd-static-route
Add a static file serving route for a URL prefix and filesystem path.
Serves files from the filesystem when the URL matches the prefix. Automatically detects MIME types based on file extension.
Parameters:
router - Router recordurl-prefix - URL prefix to match (e.g., "/static")fs-path - Filesystem path to serve files from
Returns: Updated Router with the static route added.
Router -> String -> String -> Router
router = add-static-route router "/static" "./public"
# GET /static/style.css -> serves ./public/style.cssadd-middleware
Add Ring-style middleware to wrap the request handling pipeline.
Ring-style middleware is a higher-order function that takes a handler and returns a new handler. Middleware are applied in order, with the first added being the outermost layer.
Parameters:
router - Router recordmw - Middleware function- (handler -> handler)
Returns: Updated Router with the middleware added.
Router -> ((Request -> Response) -> (Request -> Response)) -> Router
wrap-logging = fn(handler) =>
fn(req) =>
println "Request: ${req.path}"
response = handler req
println "Response: ${response.status}"
response
router = add-middleware router wrap-loggingmake-csrf-filter
Create a CSRF protection filter using Sec-Fetch-Site header validation.
Uses the Sec-Fetch-Site header to validate that state-changing requests (POST, PUT, DELETE, PATCH) come from same-origin or same-site sources.
Parameters:
options - Options record with optional exclude-paths field{exclude-paths- ["/webhook", "/api/public"]}
Returns: Filter function that validates CSRF for state-changing requests.
{exclude-paths: [String]} -> (Request -> Request | Response)
csrf-filter = make-csrf-filter {exclude-paths: ["/webhook"]}
router = add-before-filter router csrf-filtersimple-csrf-filter
Simple CSRF protection filter with no exclusions.
Returns: Filter function that validates CSRF for all state-changing requests.
Request -> Request | Response
router = add-before-filter router simple-csrf-filterhttp-get
Register a GET route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-post
Register a POST route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-put
Register a PUT route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-delete
Register a DELETE route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-patch
Register a PATCH route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-head
Register a HEAD route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
http-options
Register an OPTIONS route handler.
Parameters:
router - Router recordpattern - URL patternhandler - Handler function
Returns: Updated Router
Router -> String -> (Request -> Response) -> Router
find-route
Find a matching route for the given method and path.
Searches the router's routes for a match with the given method and path. Returns the matching route and extracted path parameters if found.
Parameters:
router - Router recordmethod - HTTP method to matchpath - URL path to match
Returns: Some (route, params) if match found, None otherwise.params is a Map of parameter names to values.
Router -> String -> String -> Option (Route, Map String String)
match find-route router "GET" "/users/123"
| Some (route, params) ->
# params contains {id: "123"} for pattern "/users/:id"
| None -> # No route foundget-error-handler
Get a custom error handler for a status code if registered.
Parameters:
router - Router recordstatus-code - HTTP status code
Returns: Some handler if custom handler registered, None otherwise
Router -> Int -> Option (Request -> Response)
default-error-response
Create a default error response for the given status code.
Parameters:
status-code - HTTP status code
Returns: Plain text error response with standard message for the status code.
Int -> Response
resp = default-error-response 404 # "Not Found"handle-error
Handle an error with optional custom error handler.
Calls custom error handler if registered, otherwise returns default error response.
Parameters:
router - Router recordrequest - Request recordstatus-code - HTTP status code to handle
Returns: Error response from custom handler or default error response.
Router -> Request -> Int -> Response
resp = handle-error router req 404route-request
Route a request through the router and return a response.
Processes a request through the full routing pipeline: 1. Applies Ring-style middleware (outermost first) 2. Runs before filters (can short-circuit) 3. Matches route and calls handler (or serves static file) 4. Runs after filters on response
Parameters:
router - Router recordrequest - Request record
Returns: Response record from handler or error handler.
Router -> Request -> Response
response = route-request router requestmake-handler
Create a handler function from the router for composition or testing.
Creates a standalone handler function that can be composed with other middleware or used for testing without starting a server.
Parameters:
router - Router record
Returns: Handler function (request -> response) with all middleware applied.
Router -> (Request -> Response)
handler = make-handler router
response = handler request