Translate remote failures without Java glue
This builds on declarative REST. Use the same HTTP runtime
and an application-provided HttpAdapterClient plus the catalog.base-url property. The following complete
model is compiled and exercised by RestErrorExecutionTest against a local HTTP
server; no external API or database is required.
namespace example.errors;
value Title(String);
value Rejection = DENIED | MISSING_REASON;
port Catalog { Title load(); Title? lookup(); void ping(); }
adapter CatalogHttp implements Catalog {
configuration {
URI baseUri from "catalog.base-url";
}
rest {
client http;
base baseUri;
Title load() {
GET "/catalog";
response Payload using toTitle;
errors {
response Problem;
when status = 422 and response.code = "DENIED"
then reject Rejection.DENIED "The request was declined";
when status = 400 and not (response.code = "KNOWN")
then reject Rejection.DENIED "Unrecognized business code";
when status = 409 and response.meta.reason is absent
then reject Rejection.MISSING_REASON "No reason was supplied";
when status = 429 and (response.attempts >= 2 or response.permanent = true)
then fail remote RATE_LIMITED "The remote service is limiting requests";
when status = 401
then fail local AUTHENTICATION "Check the configured credentials";
when status = 403
then fail unknown AUTHORIZATION "Access was denied; responsibility is unresolved";
when status = 422
then fail remote INVALID_RESPONSE "Unrecognized rejection response";
otherwise default;
}
}
Title? lookup() {
GET "/catalog";
response Payload using toTitle;
on 404 absent;
errors {
when status = 401 then fail local AUTHENTICATION "Check credentials";
}
}
void ping() {
DELETE "/catalog";
errors {
when status = 409 then fail remote UNEXPECTED_STATUS "Delete conflict";
}
}
}
schema Payload(String title);
schema Problem(String? code, Meta? meta, int? attempts, boolean? permanent);
schema Meta(String? reason);
mapping toTitle: Title <- Payload { string <- source.title; }
}
The first matching rule wins. The 422 rules demonstrate priority: the known code
becomes a PortRejectedException carrying Rejection.DENIED; an unfamiliar code
uses the later technical rule. A business code is an actual declared enum value,
not the external string and not a code invented by the mapping.
The explicit 401 rule reflects an application-specific agreement that these
credentials belong to this integration. HTTP 401 alone does not establish that.
403 deliberately leaves responsibility unresolved. Callers can distinguish
LocalIntegrationException, RemoteIntegrationException,
PortInvocationException and PortRejectedException without parsing messages.
Try these responses
| HTTP status and JSON body | Expected outcome |
|---|---|
200, {"title":"Notebook"} |
Valid Title |
422, {"code":"DENIED"} |
Business rejection DENIED |
422, {"code":"OTHER"} |
Remote INVALID_RESPONSE |
400, {"code":"OTHER"} |
Business rejection DENIED |
400, {} |
Default unexpected-status failure; missing code does not satisfy NOT |
| 400, invalid JSON | Default status failure with suppressed decoding diagnostic |
409, {} |
Business rejection MISSING_REASON |
| 409, invalid JSON | Default status failure; unreadable is not absent |
429, {"attempts":2} |
Explicit remote RATE_LIMITED |
429, {"permanent":true} |
Explicit remote RATE_LIMITED, even without attempts |
| 401, invalid JSON | Local AUTHENTICATION plus suppressed decoding diagnostic |
| 503, invalid JSON | Default remote failure plus suppressed decoding diagnostic |
| 200, invalid JSON | Remote INVALID_RESPONSE; error rules do not run on success |
Optional fields use three-valued comparisons. Missing data does not compare equal
or unequal to a literal. not preserves unknown; false and unknown is false,
true or unknown is true. Explicit is absent checks a successfully decoded DTO.
A malformed whole body makes all response predicates unknown, including absence.
Exception context identifies the port, adapter, operation and invocation. Status and Retry-After remain available as transport details. No response body, credentials or request arguments are automatically copied into exceptions or logs. Declarative messages are fixed text. Use the Java error hook if deliberate, selected enrichment is needed; choose one error-translation mode per operation.
No rule requests a retry. In particular, rate limiting and communication failures do not prove that repeating an operation is safe.
For optional results, on 404 absent remains explicit. A declarative rule alongside
it must exclude 404 (for example status != 404 and ...); otherwise the compiler
reports that the rule could be shadowed. There is no automatic 404-to-empty policy.
See the REST contract for predicate types, precedence and limitations. Continue with the application demo runner; its database-free test and full application mode both exercise the generated HTTP boundary.