rest

The rest library provides a small REST authoring layer on top of the existing http_router library. It is implemented as a category so that REST application objects can continue to implement the http_handler_protocol protocol directly while reusing the existing router dispatch, request annotation, middleware, and OpenAPI derivation logic.

This library reuses the http_core and http_router. It derives router hooks from higher-level endpoint descriptors, provides small request and response helpers, and normalizes simple action result terms into HTTP responses.

Layering

rest is the highest-level authoring layer in the current HTTP stack:

  • Start with http_core library for normalized HTTP messages and with the http_router library for declarative routing.

  • Use the rest library when you want to describe endpoints with endpoint/5, keep the inherited handle/2 entry point, and return small result terms instead of building every response manually.

  • Stay with the http_router library when you need direct control over route/4, custom handler predicates, or router hooks without the higher-level endpoint layer.

  • Pair REST applications with the open_api library when you want derived OpenAPI documents or opt-in request and response contract validation.

API documentation

Open the ../../apis/library_index.html#rest link in a web browser.

Loading

To load the library, load the loader.lgt file:

| ?- logtalk_load(rest(loader)).

Testing

To test this library, load the tester.lgt file:

| ?- logtalk_load(rest(tester)).

Current scope

The current version provides one imported routing predicate through the category inheritance from http_router plus request and response helper predicates:

  • handle/2

  • path_parameter/3

  • query_parameter/3

  • request_header/3

  • request_body/2

  • json_body/2

  • json_object_body/2

  • json_array_body/2

  • form_body/2

  • text_body/2

  • binary_body/2

  • json_response/4-5

  • created_response/4

  • no_content_response/2

  • problem_response/6

The category also exposes optional OpenAPI contract validation hooks:

  • open_api_validate_request/1

  • open_api_request_validation_error_response/4

  • open_api_validate_response/1

  • open_api_response_validation_error_response/5

The category also exposes optional action failure and error normalization hooks:

  • rest_action_failure_response/3

  • rest_action_error_response/4

Importing REST application objects are expected to define endpoint descriptors using:

  • endpoint(Id, Method, Path, Action, Options)

Endpoint identifiers must be unique within the importing object. When the object also exposes OpenAPI operations through http_router, those same identifiers are reused as operation identifiers.

The Action argument is the name of a declared local predicate with arity 2, typically a protected predicate. The action predicate receives the annotated request and returns or throws one of the currently supported result terms:

  • ok(JSON)

  • created(Location, JSON)

  • no_content

  • json(Status, JSON)

  • json(Status, Headers, JSON)

  • problem(Status, Type, Title, Detail)

  • response(Response) or a normalized response/5 term directly

The category derives the http_router hooks from those endpoint descriptors:

  • route/4

  • route_metadata/2

  • route_produces/2

All matched routes dispatch through a single internal action runner that calls the endpoint action and normalizes its returned or thrown result.

Endpoint options are passed through as route metadata except for the special produces(MediaTypes) option, which is translated into route_produces/2. This keeps endpoint metadata available to action predicates and response middleware and also allows router-level OpenAPI derivation to keep working.

That same metadata pass-through also allows REST applications to reuse router companion categories. For example, an object can import http_router_digest_auth(_, _, _), declare digest_auth(Options) in endpoint options, delegate authorize_routed_request/2 to authorize_digest_auth_request/2, and register add_digest_authentication_info/3 as response middleware.

The current helper predicates read the normalized HTTP request terms provided by http_core:

request(Method, Target, Version, Headers, Body, Properties)

They rely on router annotations such as route/1 and path_params/1 plus the normalized derived properties produced by the http_core library such as query_pairs/1. The path_parameter/3 and query_parameter/3 helpers are deterministic lookups. The decoded body helpers return 400 Bad Request problem responses when the current action expects a JSON, form, text, or binary body and the normalized request body is missing or has a different decoded shape. The json_object_body/2 and json_array_body/2 helpers build on json_body/2 and additionally require the decoded JSON payload to be an object or array term respectively.

Action result normalization currently builds normalized HTTP response terms from returned or thrown supported result terms using these rules:

  • successful JSON responses use the negotiated router media type when the request carries a JSON-compatible response_media_type/1 annotation and otherwise fall back to application/json

  • problem responses always use application/problem+json

Plain action failure and unsupported exceptions still default to generic 500 Internal Server Error problem responses, but importing objects can override those defaults using rest_action_failure_response/3 and rest_action_error_response/4.

When open_api_validate_request/1 succeeds for a matched endpoint, the category validates the normalized request against the derived OpenAPI operation descriptor before calling the endpoint action. When open_api_validate_response/1 succeeds for a matched endpoint, the category validates the normalized response after action result normalization.

By default, request validation failures preserve three classes of client error: unsupported request media types return 415 Unsupported Media Type, schema-invalid request bodies return 422 Unprocessable Content, and other request validation failures return 400 Bad Request. Response validation failures still return a 500 Internal Server Error application/problem+json response. Importing objects can override those defaults using the corresponding validation error response hooks. Validation is skipped for the synthetic router requests used internally for OpenAPI request and response inference.

OpenAPI integration

When an importing object also implements the open_api_provider_protocol protocol, the inherited http_router OpenAPI derivation continues to work. Endpoint options such as summary/1, description/1, tags/1, deprecated/1, security/1, parameters/1, request_body/1, and responses/1 are exposed through route_metadata/2, while produces(MediaTypes) drives route_produces/2 and therefore response media type inference.

Current limitations

  • Request and response OpenAPI contract validation is opt-in per endpoint using the validation hooks

  • The library does not include dedicated pagination, filtering, or rate-limiting helpers of its own

  • Result normalization focuses on JSON and problem responses; it does not provide specialized helpers for redirects, streaming, or multipart payloads