Edda.Spec
Optional endpoint specification and OpenAPI output helpers.
The core router API is still Edda.route, choose, group, and mount;
this module builds typed route contracts that can also emit OpenAPI.
The builder produces a RouteSchema, which the router consumes for dispatch
and a spec generator consumes for OpenAPI emission. One definition, two
extractors, no runtime overhead on the dispatch path.
Schemas are explicit SchemaFor a witnesses. JsonContract a pairs a schema
with an encoder for responses, and JsonDecoderContract a pairs a schema
with a decoder for request bodies.
Types
Status
type Status =
| Success
| Created
| NoContent
| BadRequest
| Unauthorized
| Forbidden
| NotFound
| Conflict
| Unprocessable
| InternalError
| Other Int
deriving (Eq, Debug)Action
type alias Action = Request -> ResponseTypedAction
type alias TypedAction a = Request -> TypedResponse aTypedResponse
type TypedResponse a =
| TypedResponse ResponseJsonSchema
type JsonSchema =
| StringSchema
| IntSchema
| FloatSchema
| BoolSchema
| ArraySchema JsonSchema
| ObjectSchema (List (String, JsonSchema)) (List String)
| NullableSchema JsonSchema
| ComponentSchema String JsonSchemaSchemaFor
type SchemaFor a =
| SchemaFor JsonSchemaJsonContract
record JsonContract a {
schema: SchemaFor a,
encode: a -> Json
}JsonDecoderContract
record JsonDecoderContract a {
schema: SchemaFor a,
decode: Json -> Result a J.Error
}Schema
type Schema =
| SchemaContentSpec
record ContentSpec {
content_type: String,
schema: Maybe JsonSchema,
description: Maybe String
}ParamMeta
record ParamMeta {
name: String,
description: String
}RequestHeaderMeta
record RequestHeaderMeta {
name: String,
description: String,
schema: JsonSchema,
required: Bool
}ResponseHeaderMeta
record ResponseHeaderMeta {
name: String,
description: String,
schema: JsonSchema
}BodyMeta
record BodyMeta {
content_type: String,
description: String,
schema: Maybe JsonSchema
}ResponseMeta
record ResponseMeta {
content_type: String,
description: Maybe String,
schema: Maybe JsonSchema,
headers: List ResponseHeaderMeta
}EndpointInput
record EndpointInput input {
body: Maybe BodyMeta,
decode: Request -> Result input EJ.BodyError,
documents_decode_failure: Bool
}RouteSchema
record RouteSchema {
method: Method,
path: String,
operation_id: Maybe String,
summary: Maybe String,
description: Maybe String,
tags: List String,
params: List ParamMeta,
request_headers: List RequestHeaderMeta,
body: Maybe BodyMeta,
responses: List (Status, ResponseMeta),
action: Request -> Response
}RouteBuilder
record RouteBuilder a {
method: Method,
path: String,
params: List ParamMeta,
body: Maybe BodyMeta,
responses: List (Status, ResponseMeta)
}EndpointStart
record EndpointStart {
method: Method,
path: String,
params: List ParamMeta
}EndpointBuilder
record EndpointBuilder input {
method: Method,
path: String,
params: List ParamMeta,
input: EndpointInput input
}EndpointReady
record EndpointReady input output {
method: Method,
path: String,
params: List ParamMeta,
input: EndpointInput input,
response_status: Status,
response_contract: JsonContract output,
response_description: String,
extra_responses: List (Status, ResponseMeta)
}EndpointResultReady
record EndpointResultReady input output err {
ready: EndpointReady input output,
error_status: Status,
error_contract: JsonContract err
}OpenApiInfo
record OpenApiInfo {
title: String,
version: String
}Functions
status_code
fun status_code : Status -> Intuntyped
fun untyped : Response -> TypedResponse aok
fun ok : a -> TypedResponse a where {a: ToJson}ok_status
fun ok_status : Status -> a -> TypedResponse a where {a: ToJson}text_response
fun text_response : Status -> String -> TypedResponse ano_content
fun no_content : TypedResponse aschema_for
fun schema_for : JsonSchema -> SchemaFor aschema_json
fun schema_json : SchemaFor a -> JsonSchemanamed_schema
fun named_schema : String -> SchemaFor a -> SchemaFor aWrap a schema with an OpenAPI component name. Named schemas render as $ref
at use sites and are emitted under components.schemas.
json_contract
fun json_contract : SchemaFor a -> a -> Json -> JsonContract anamed_json_contract
fun named_json_contract : String -> SchemaFor a -> a -> Json -> JsonContract aBuild a named JSON contract from an explicit schema and encoder.
json_contract_for
fun json_contract_for : SchemaFor a -> JsonContract a where {a: ToJson}named_json_contract_for
fun named_json_contract_for : String -> SchemaFor a -> JsonContract a where {a: ToJson}Bridge for named response contracts backed by a hand-written ToJson impl.
array_contract
fun array_contract : JsonContract a -> JsonContract (List a)nullable_contract
fun nullable_contract : JsonContract a -> JsonContract (Maybe a)contract_schema
fun contract_schema : JsonContract a -> SchemaFor acontract_schema_json
fun contract_schema_json : JsonContract a -> JsonSchemacontract_encode
fun contract_encode : JsonContract a -> a -> Jsonjson_response
fun json_response : Int -> JsonContract a -> a -> Responseok_json
fun ok_json : JsonContract a -> a -> TypedResponse aok_json_status
fun ok_json_status : Status -> JsonContract a -> a -> TypedResponse ajson_decoder_contract
fun json_decoder_contract : SchemaFor a -> Json -> Result a J.Error -> JsonDecoderContract anamed_json_decoder_contract
fun named_json_decoder_contract : String -> SchemaFor a -> Json -> Result a J.Error -> JsonDecoderContract aBuild a named JSON decoder contract from an explicit schema and decoder.
json_decoder_contract_for
fun json_decoder_contract_for : SchemaFor a -> JsonDecoderContract a where {a: FromJson}named_json_decoder_contract_for
fun named_json_decoder_contract_for : String -> SchemaFor a -> JsonDecoderContract a where {a: FromJson}Bridge for named request-body contracts backed by a hand-written FromJson
impl.
decoder_contract_schema
fun decoder_contract_schema : JsonDecoderContract a -> SchemaFor adecoder_contract_schema_json
fun decoder_contract_schema_json : JsonDecoderContract a -> JsonSchemacontract_decode
fun contract_decode : JsonDecoderContract a -> Json -> Result a J.Errorbody_json_contract
fun body_json_contract : JsonDecoderContract a -> Request -> Result a EJ.BodyErrorstring_schema
fun string_schema : SchemaFor Stringint_schema
fun int_schema : SchemaFor Intfloat_schema
fun float_schema : SchemaFor Floatbool_schema
fun bool_schema : SchemaFor Boolarray_of
fun array_of : SchemaFor a -> SchemaFor (List a)nullable_of
fun nullable_of : SchemaFor a -> SchemaFor (Maybe a)object_of
fun object_of : List (String, JsonSchema) -> SchemaFor aoptional
fun optional : String -> SchemaFor a -> SchemaFor aschema
fun schema : Schemaschema_into
fun schema_into : a -> b -> SchemaFor (a -> b)schema_field
fun schema_field : String -> SchemaFor a -> SchemaFor (a -> b) -> SchemaFor bjson_of
fun json_of : SchemaFor a -> ContentSpecjson_contract_content
fun json_contract_content : JsonContract a -> ContentSpecjson_decoder_contract_content
fun json_decoder_contract_content : JsonDecoderContract a -> ContentSpecplain_text
fun plain_text : ContentSpecno_body_spec
fun no_body_spec : ContentSpecdescribe
fun describe : String -> ContentSpec -> ContentSpecendpoint
fun endpoint : Method -> String -> EndpointStartStart a spec-owned endpoint adapter for a method and path.
Pair this with no_body or json_input, then declare the success
response with returns_json and attach a domain handler with handled_by or
handled_by_result.
endpoint_path_param
fun endpoint_path_param : String -> String -> EndpointStart -> EndpointStartDocument a path parameter on an endpoint.
no_body
fun no_body : EndpointStart -> EndpointBuilder UnitDeclare that an endpoint has no request body.
The domain handler receives () as its decoded input.
json_input
fun json_input : JsonDecoderContract input -> String -> EndpointStart -> EndpointBuilder inputDeclare that an endpoint decodes one JSON request body using a decoder contract.
returns_json
fun returns_json : Status -> JsonContract output -> String -> EndpointBuilder input -> EndpointReady input outputDeclare the successful JSON response for a spec-owned endpoint.
The stored response contract is used for both OpenAPI output and runtime
encoding in handled_by / handled_by_result.
responds_with
fun responds_with : Status -> ContentSpec -> EndpointReady input output -> EndpointReady input outputAdd extra response metadata to a spec-owned endpoint.
This documents alternate statuses such as Conflict or NotFound. Runtime
behavior for those statuses is still supplied by the handler or error mapper.
returns_error_json
fun returns_error_json : Status -> JsonContract err -> String -> EndpointReady input output -> EndpointResultReady input output errDeclare a typed JSON error response for a result-returning endpoint.
Pair this with handled_by_result_json. The documented error schema and
runtime error encoder come from the same JsonContract.
handled_by
fun handled_by : Request -> input -> output -> EndpointReady input output -> RouteSchemaAttach a domain handler to a spec-owned endpoint.
Edda decodes the configured endpoint input, calls the handler, then encodes
the returned output with the configured JsonContract.
handled_by_result
fun handled_by_result : err -> Response -> Request -> input -> Result output err -> EndpointReady input output -> RouteSchemaAttach a domain handler that can return application errors.
Successful outputs are still encoded by the endpoint's JsonContract.
Errors are mapped to ordinary Response values by caller-provided code.
handled_by_result_json
fun handled_by_result_json : Request -> input -> Result output err -> EndpointResultReady input output err -> RouteSchemaAttach a domain handler that returns a typed JSON application error.
Successful outputs are encoded by the endpoint's success JsonContract.
Errors are encoded by the error JsonContract declared with
returns_error_json.
route
fun route : Method -> String -> RouteBuilder apath_param
fun path_param : String -> String -> RouteBuilder a -> RouteBuilder ajson_body
fun json_body : SchemaFor body -> String -> RouteBuilder a -> RouteBuilder ajson_body_contract
fun json_body_contract : JsonDecoderContract body -> String -> RouteBuilder a -> RouteBuilder aresponds
fun responds : Status -> ContentSpec -> RouteBuilder a -> RouteBuilder aresponds_success_json
fun responds_success_json : Status -> SchemaFor a -> String -> RouteBuilder a -> RouteBuilder aresponds_success_json_contract
fun responds_success_json_contract : Status -> JsonContract a -> String -> RouteBuilder a -> RouteBuilder aperformed_by
fun performed_by : Request -> TypedResponse a -> RouteBuilder a -> RouteSchemaas_route
fun as_route : RouteSchema -> Request -> Response needs {Skip}operation_id
fun operation_id : String -> RouteSchema -> RouteSchemaSet the OpenAPI operationId for a route schema.
summary
fun summary : String -> RouteSchema -> RouteSchemaSet the OpenAPI summary for a route schema.
description
fun description : String -> RouteSchema -> RouteSchemaSet the OpenAPI description for a route schema.
tag
fun tag : String -> RouteSchema -> RouteSchemaAdd an OpenAPI tag to a route schema.
requires_header
fun requires_header : String -> SchemaFor a -> String -> RouteSchema -> RouteSchemaDocument a required request header for this operation.
This is OpenAPI metadata only; it does not validate requests at runtime.
optional_header
fun optional_header : String -> SchemaFor a -> String -> RouteSchema -> RouteSchemaDocument an optional request header for this operation.
This is OpenAPI metadata only; it does not validate requests at runtime.
response_header
fun response_header : Status -> String -> SchemaFor a -> String -> RouteSchema -> RouteSchemaDocument a response header for a specific status.
If the route does not document that status yet, this helper leaves the route
unchanged. Add the response first with returns_json, responds, or
responds_with.
returns_header
fun returns_header : String -> SchemaFor a -> String -> RouteSchema -> RouteSchemaDocument a header on the first documented response, normally the success
response declared by returns_json or responds_success_json_contract.
responds_with_header
fun responds_with_header : Status -> String -> SchemaFor a -> String -> RouteSchema -> RouteSchemaAlias for response_header, useful next to responds_with.
to_openapi_json
fun to_openapi_json : OpenApiInfo -> List RouteSchema -> Jsonto_openapi
fun to_openapi : OpenApiInfo -> List RouteSchema -> Stringscalar_html
fun scalar_html : String -> Stringscalar_handler
fun scalar_handler : String -> Request -> Response