Skip to content

05 - Errors ​

Rayfold separates protocol errors (a fixed code set, the same in every API) from domain errors (declared per operation in the schema, delivered as typed payloads). Clients get exhaustive unions; operators get uniform codes for retries, alerting and HTTP status mapping.

1. Error object ​

json
{
  "code": "domain",
  "type": "OutOfStock",
  "message": "Only 1 copy left",
  "data": { "bookId": "b1", "available": 1 },
  "path": "items.0",
  "retryable": false
}
MemberRequiredMeaning
codeyesOne of the codes in §2, or domain.
typewhen code is domainThe error type name from the schema.
messageyesHuman-readable, not for programmatic use.
datawhen type is setPayload matching the error type's fields.
pathnoResult path the error applies to (partial errors).
retryablenoServer hint, sent only when it differs from the default. A receiver that sees none derives it: true for unavailable, deadline_exceeded and aborted, false otherwise.

2. Protocol codes ​

The 16 codes of gRPC/Connect, verbatim, so existing tooling and intuitions carry over:

canceled, unknown, invalid_argument, deadline_exceeded, not_found, already_exists, permission_denied, resource_exhausted, failed_precondition, aborted, out_of_range, unimplemented, internal, unavailable, data_loss, unauthenticated.

Plus domain for declared errors.

3. HTTP status derivation ​

Used for a single-frame JSON response (Accept: application/json, one op, one frame) and for a problem document a server answers before it parses a batch. Frame streams are always 200.

CodeStatus
invalid_argument, failed_precondition, out_of_range400
unauthenticated401
permission_denied403
not_found404
already_exists, aborted409
resource_exhausted429
canceled499
unimplemented501
unavailable503
deadline_exceeded504
domain422
everything else500

Two protocol errors carry a type: DependencyFailed (a $ref target failed, 03 §2) and VersionConflict (a conditional command saw a newer version, 03 §4a); both use failed_precondition. HTTP bindings answer VersionConflict with 412 Precondition Failed.

4. Atomic by default, partial by declaration ​

An operation either succeeds completely or ends with one error frame. There is no "data plus errors" in Core, except for fields annotated @partial in the schema or the shape: such a field becomes null and an error object with its path is appended to the frame's errors list. Clients can therefore trust every non-partial field.

5. Declared errors ​

A command's throws list is a closed union. A resolver MUST NOT raise a domain error that is not declared; the runtime converts an undeclared one to internal. The check is made on commands, the operations whose errors clients branch on. Queries and streams MAY declare throws too; their declarations are documentation, not an enforced closed set. Generated clients expose the union as a discriminated type on error.type.

6. Problem Details ​

Refusals a server makes before it parses a batch, and every response from an HTTP binding (04 §8), use RFC 9457. A batch that parses and then fails as a whole (over budget, for one) is an error frame on the frame channel instead, because by then there is a frame channel to put it on.

json
{ "type": "https://eddyboutros.github.io/rayfold/errors/invalid_argument", "title": "invalid argument", "status": 400,
  "detail": "placeOrder().input.qty: expected Int", "code": "invalid_argument" }

title is the problem type with underscores replaced by spaces. A binding's problem document may also carry path and data. A declared domain error identifies itself by its own type rather than by the protocol code: type is the base URI plus the error's name, and title is that name.

json
{ "type": "https://eddyboutros.github.io/rayfold/errors/OutOfStock", "title": "OutOfStock", "status": 422,
  "detail": "Only 2 left", "code": "domain", "data": { "available": 2 } }

Released under the Apache-2.0 license.