SagaSaga
Edda

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 -> Response

TypedAction

type alias TypedAction a = Request -> TypedResponse a

TypedResponse

type TypedResponse a =
  | TypedResponse Response

JsonSchema

type JsonSchema =
  | StringSchema
  | IntSchema
  | FloatSchema
  | BoolSchema
  | ArraySchema JsonSchema
  | ObjectSchema (List (String, JsonSchema)) (List String)
  | NullableSchema JsonSchema
  | ComponentSchema String JsonSchema

SchemaFor

type SchemaFor a =
  | SchemaFor JsonSchema

JsonContract

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 =
  | Schema

ContentSpec

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 -> Int

untyped

fun untyped : Response -> TypedResponse a

ok

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 a

no_content

fun no_content : TypedResponse a

schema_for

fun schema_for : JsonSchema -> SchemaFor a

schema_json

fun schema_json : SchemaFor a -> JsonSchema

named_schema

fun named_schema : String -> SchemaFor a -> SchemaFor a

Wrap 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 a

named_json_contract

fun named_json_contract : String -> SchemaFor a -> a -> Json -> JsonContract a

Build 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 a

contract_schema_json

fun contract_schema_json : JsonContract a -> JsonSchema

contract_encode

fun contract_encode : JsonContract a -> a -> Json

json_response

fun json_response : Int -> JsonContract a -> a -> Response

ok_json

fun ok_json : JsonContract a -> a -> TypedResponse a

ok_json_status

fun ok_json_status : Status -> JsonContract a -> a -> TypedResponse a

json_decoder_contract

fun json_decoder_contract : SchemaFor a -> Json -> Result a J.Error -> JsonDecoderContract a

named_json_decoder_contract

fun named_json_decoder_contract : String -> SchemaFor a -> Json -> Result a J.Error -> JsonDecoderContract a

Build 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 a

decoder_contract_schema_json

fun decoder_contract_schema_json : JsonDecoderContract a -> JsonSchema

contract_decode

fun contract_decode : JsonDecoderContract a -> Json -> Result a J.Error

body_json_contract

fun body_json_contract : JsonDecoderContract a -> Request -> Result a EJ.BodyError

string_schema

fun string_schema : SchemaFor String

int_schema

fun int_schema : SchemaFor Int

float_schema

fun float_schema : SchemaFor Float

bool_schema

fun bool_schema : SchemaFor Bool

array_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 a

optional

fun optional : String -> SchemaFor a -> SchemaFor a

schema

fun schema : Schema

schema_into

fun schema_into : a -> b -> SchemaFor (a -> b)

schema_field

fun schema_field : String -> SchemaFor a -> SchemaFor (a -> b) -> SchemaFor b

json_of

fun json_of : SchemaFor a -> ContentSpec

json_contract_content

fun json_contract_content : JsonContract a -> ContentSpec

json_decoder_contract_content

fun json_decoder_contract_content : JsonDecoderContract a -> ContentSpec

plain_text

fun plain_text : ContentSpec

no_body_spec

fun no_body_spec : ContentSpec

describe

fun describe : String -> ContentSpec -> ContentSpec

endpoint

fun endpoint : Method -> String -> EndpointStart

Start 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 -> EndpointStart

Document a path parameter on an endpoint.

no_body

fun no_body : EndpointStart -> EndpointBuilder Unit

Declare 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 input

Declare 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 output

Declare 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 output

Add 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 err

Declare 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 -> RouteSchema

Attach 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 -> RouteSchema

Attach 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 -> RouteSchema

Attach 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 a

path_param

fun path_param : String -> String -> RouteBuilder a -> RouteBuilder a

json_body

fun json_body : SchemaFor body -> String -> RouteBuilder a -> RouteBuilder a

json_body_contract

fun json_body_contract : JsonDecoderContract body -> String -> RouteBuilder a -> RouteBuilder a

responds

fun responds : Status -> ContentSpec -> RouteBuilder a -> RouteBuilder a

responds_success_json

fun responds_success_json : Status -> SchemaFor a -> String -> RouteBuilder a -> RouteBuilder a

responds_success_json_contract

fun responds_success_json_contract : Status -> JsonContract a -> String -> RouteBuilder a -> RouteBuilder a

performed_by

fun performed_by : Request -> TypedResponse a -> RouteBuilder a -> RouteSchema

as_route

fun as_route : RouteSchema -> Request -> Response needs {Skip}

operation_id

fun operation_id : String -> RouteSchema -> RouteSchema

Set the OpenAPI operationId for a route schema.

summary

fun summary : String -> RouteSchema -> RouteSchema

Set the OpenAPI summary for a route schema.

description

fun description : String -> RouteSchema -> RouteSchema

Set the OpenAPI description for a route schema.

tag

fun tag : String -> RouteSchema -> RouteSchema

Add an OpenAPI tag to a route schema.

requires_header

fun requires_header : String -> SchemaFor a -> String -> RouteSchema -> RouteSchema

Document 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 -> RouteSchema

Document 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 -> RouteSchema

Document 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 -> RouteSchema

Document 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 -> RouteSchema

Alias for response_header, useful next to responds_with.

to_openapi_json

fun to_openapi_json : OpenApiInfo -> List RouteSchema -> Json

to_openapi

fun to_openapi : OpenApiInfo -> List RouteSchema -> String

scalar_html

fun scalar_html : String -> String

scalar_handler

fun scalar_handler : String -> Request -> Response