Skip to main content

Module rpc

Module rpc 

Source
Expand description

Runtime primitives for the transport rpc generation style.

See docs/design/rpc-transport.md for the full design. This module provides the binding-side surface that schemas with transport rpc generate against:

  • POST /rpc/{op_id} — unary calls. Body is the codec-encoded input (no frame wrapper); response body is the codec-encoded output on success, or an RpcErrorBody on error with HTTP status mapped via [CratestackError::status_code].
  • POST /rpc/batch — sequence of RpcRequest frames in, sequence of RpcResponseFrame frames out in the same order. Per-frame errors don’t poison the batch.
  • GET /rpc/subscribe/{op_id} — SSE subscription dispatch for @@subscribed models (design doc §3.4a, cratestack#390). One event: message per ModelEvent<T>, terminated by one event: error on backpressure overflow. See [sse] and [subscription_bridge].

The full WebSocket frame loop (§3.4) remains speced but unbuilt, gated on a real bidirectional/high-multiplexing need per issue #183’s spike decision — see docs/design/rpc-transport.md §6.5.

The macro emits the dispatch table and the rpc_router constructor. This crate provides the shared frame shapes, error mapping, and the RPC_*_PATH constants both sides agree on.

Structs§

RpcErrorBody
Wire shape of a single error returned by an RPC call. Maps from CratestackError via rpc_code + CratestackError::public_message.
RpcGetInput
RPC input for model.<X>.get. Deliberately its own struct rather than reusing RpcPkInput for both get and delete: delete also decodes RpcPkInput, and adding selection fields there would be a silently- ignored surface on a verb that has no response body to carry projections into. Carries the same selection surface as REST GET /<plural>/{id}fields, include, and includeFields (line-for-line parity; on transport rpc the RPC dispatcher synthesizes a URL query string from these fields and runs the macro-generated parse_model_fetch_query parser to enforce selection validation and projection in one path).
RpcListInput
RPC input for model.<X>.list. Mirrors the REST URL query 1:1 — every optional field maps to a query param of the same name, predicates carry arbitrary (key, value) pairs that aren’t reserved keywords.
RpcListPredicate
Single arbitrary key/value predicate inside RpcListInput::filters. Models the REST URL form’s “anything that isn’t a reserved keyword is a predicate” rule (e.g. ?published=true&authorId=42).
RpcPkInput
RPC input for model.<X>.delete. The PK type is instantiated per-model at the macro emission site.
RpcRequest
Wire shape of a single batch request frame.
RpcResponseFrame
Wire shape of a single batch response frame.
RpcUpdateInput
RPC input for model.<X>.update. Parameterized on both the PK type and the model’s concrete Update<X>Input so the patch decodes straight to its real type — round-tripping through serde_json::Value would corrupt CBOR Option::None values (which minicbor-serde encodes as 0xf6 simple-null but serde_json::Value encodes as the CBOR empty-array marker; see comments in cratestack-codec-cbor). The dispatcher re-encodes patch through the same codec before handing it to the existing update handler.
SubscriptionPush
Handed to one or more CratestackEventBus::subscribe callbacks registered for the same logical subscription (e.g. one per @@emitted operation on a model). Every clone shares the same underlying sender slot, so the first overflow observed by any of them permanently closes the channel — subsequent pushes from any clone become silent no-ops.

Constants§

RPC_BATCH_PATH
Mount path for batched RPC calls. Body is a codec-encoded sequence of RpcRequest frames.
RPC_BINDING_CAPABILITIES
Codec/transport capabilities for every RPC binding route. Both unary and batch accept and emit CBOR or JSON, default CBOR; sequence responses (streaming) are not yet supported by this binding.
RPC_STREAM_ERROR_TAG
CBOR tag number reserved for the mid-stream error sentinel described in docs/design/rpc-transport.md §3.3: when a genuinely incremental application/cbor-seq sequence response (a @stream procedure, see cratestack#282/#283) fails partway through, the last item of the sequence is Tag(RPC_STREAM_ERROR_TAG, RpcErrorBody-as-CBOR-map) — CBOR major type 6, this tag number, wrapping RpcErrorBody encoded as a plain CBOR map — in place of what would otherwise be the next unwrapped out item. No further items follow it; end of body comes immediately after.
RPC_SUBSCRIBE_PATH
Mount path for @@subscribe SSE subscriptions (docs/design/rpc-transport.md §3.4a, cratestack#390). The trailing segment is the percent-decoded op id, e.g. GET /rpc/subscribe/model.User.subscribe. Unlike RPC_UNARY_PATH this is GET-only and carries no request body — auth is header-based (same as every other HTTP RPC binding), not an upgrade-time HMAC like the WS path (§3.4).
RPC_UNARY_PATH
Mount path for unary RPC calls. The trailing segment is the percent-decoded op id, e.g. POST /rpc/model.User.list.

Functions§

convert_handler_error_response
Post-process a handler-emitted response. Success responses pass through unchanged. Non-2xx responses are buffered, their bodies decoded as cratestack_core::CratestackErrorResponse (the REST shape the existing axum handlers emit), translated to RpcErrorBody with the gRPC-style code, and re-encoded with the same HTTP status.
cratestack_error_code_to_rpc_code
Map a CratestackErrorResponse.code string (screaming-snake, REST- binding vocabulary) to the stable gRPC-style code the RPC binding emits.
decode_rpc_body
Decode an RPC unary request body into T, picking the codec based on the request’s Content-Type header. Missing header → CBOR (the default for the REST binding too).
encode_model_event_sse_response
Encode items as a text/event-stream response. items ending (None from the underlying Stream) always means backpressure overflow closed the channel (see module docs) — the last byte chunk written is always the Error{unavailable} sentinel event.
encode_rpc_error
Build an axum::Response carrying an RpcErrorBody for a CratestackError raised inside the dispatcher (e.g. body decode failure, unknown op id). The HTTP status comes from CratestackError::status_code; the body is codec-encoded via the request’s codec, content-type negotiated against RPC_BINDING_CAPABILITIES.
encode_rpc_value
Encode an arbitrary serializable value back to bytes using the same codec as the request. Used by the macro-generated update dispatch arm to re-encode the typed patch before handing it to the existing update handler as Bytes.
guarded_receiver_stream
Wraps a raw mpsc::Receiver into a Stream, keeping guard alive for exactly as long as the stream is — dropped together whether the stream ends normally (overflow, see SubscriptionPush) or is cancelled mid-poll (an ordinary client disconnect just drops this whole future). This is what lets a cratestack_core::SubscriptionGuard passed as guard unsubscribe cleanly in either case without the caller needing to distinguish which one happened.
response_to_frame
Convert an [axum::Response] returned by an inner dispatch arm into a single batch response frame.
rpc_code
Map a CratestackError to its stable RPC code (gRPC-style snake_case).
subscription_channel
Builds a fresh bounded channel plus the SubscriptionPush handle callers clone into every CratestackEventBus::subscribe closure that should feed it.
synthesize_get_query
Synthesize a URL query string from an RpcGetInput in exactly the shape the macro-generated parse_model_fetch_query parses — the direct get counterpart to synthesize_list_query, so RPC get and REST GET /<plural>/{id} run one and the same validation and projection path. Returns None when no field is set.
synthesize_list_query
Synthesize a URL query string from an RpcListInput in exactly the shape the macro-generated parse_model_list_query parses. Returns None when the input has no fields set (the existing handler treats a missing query the same as an empty one — no point allocating).
validate_subscribe_accept_header
GET /rpc/subscribe/{op_id} has no upgrade handshake to negotiate a binary subprotocol like WS does — the client states its intent via a plain Accept header, same as every other HTTP RPC binding. Reject anything that doesn’t ask for SSE up front, before any CratestackEventBus subscription gets registered.