How the Crates Fit Together
Capi is spread across some eighty crates in seven workspaces and a ring of
single-crate repos around them. That sounds like a lot to hold in your head, and
the good news is you don’t have to: the framework proper arrives as one dependency
(capi_rs) behind one glob (use capi_rs::prelude::*;), and a
client names further crates only for the capabilities it actually uses.
This chapter explains why the split exists, what the facade hides, and — the part
that actually matters when you go reading source — which crates live inside the
workspaces and which are siblings you swap out.
Why so many crates
At its core Capi is a trait system with a reference implementation. The
idea behind Capi was to define a set of traits that tie together every
element needed to build an API client library — endpoints, config, transport,
decoding, auth — and to bind those elements to one another through strong types.
Once the pieces connect through traits rather than concrete types, they behave
like building blocks: you assemble the ones you need and the compiler checks that
they fit. A handful of crates (capi_core foremost, with capip_http and
capi_middleware, over the protocol-generic capi_protocol beneath them)
define those contracts; everything else — the decoders, configs, transport,
auth, interface, codecs — is one working implementation of them.
A consequence of that shape is that most pieces are replaceable: a config, a
decoder, a limiter, a client adapter, a codec — each is an ordinary type bound to
an ordinary trait, which is also why you can patch an imported
endpoint. One piece is deliberately not:
the traversal. capi_transport holds the engine that walks the pipeline, gates,
seals, transmits, and unwinds — and the seal is exactly that, a seal. The door a
decoder receives for follow-up sends carries a hidden marker trait, so an exchange
composed anywhere else is not expressible: every exchange in the framework goes
through the one traversal, which is what lets a fixture, a firewall, or a budget
stand behind all of them at once.
Several practical forces turn that philosophy into crate boundaries:
- The orphan rule. Rust won’t let you implement a foreign trait on a foreign
type. Each contract lives in its own crate so you can implement it for your
types — you can
impl Endpoint for GetUseronly becauseEndpointis incapi_core, not locked inside a third-party crate. The same rule is why a byte codec, a client adapter, or a concrete signer each gets its own crate: they implement framework traits for outside libraries (serde_json,reqwest, …). - Looser version coupling. Keeping the reference implementations in separate
crates from the core contracts means a change to a decoder, a limiter, or an
adapter doesn’t force a version bump in
capi_core. The contract crates are meant to change rarely; letting the implementation crates evolve on their own cadence keeps a wider range of versions compatible across the ecosystem instead of rippling a breaking-change wave through every dependent on each release. - Granular dependencies. A no_std client can depend on
capi_coreplus one adapter and skip the rest, keeping build times and binary size down. - The facade collapses it back.
capi_rsre-exports the Capi framework workspace under stable module names and ships a large prelude, so the framework’s internal structure is invisible to the people who just want to call an API.
The facade and the prelude
Without the facade, imports are a scavenger hunt across crates:
use capi_core::endpoint::Endpoint;
use capi_core::config::ApiConfig;
use capi_base_decoders::BodyDecoder;
use capi_base_config::BaseConfig;
use capi_base_interface::BaseInterface;
// ... and a dozen more
With the facade it is one line:
use capi_rs::prelude::*;
The prelude pulls in several hundred items grouped by category — the core traits
(Endpoint, ApiConfig, ResponseDecoder), the error taxonomy, the client and
capability vocabulary, the request context and the send outcome, decoders and
encoders, the interface, URL building, the pipeline vocabulary, rate limiting,
authorization headers, authentication, config types, the HTTP method and status
types, and the three alloc items the derives expect in scope (Cow, format,
ToString). When you need a crate by name, the facade exposes each
under a short alias (capi_rs::core, ::config, ::decoders,
::transport, ::extras, …), and passes the vocabulary a client’s own users need
on through capi_rs::reexport for client libraries to forward downstream.
What the facade covers is the Capi framework workspace. The wire model, the format markers, and the capability crates outside it are named by the client that uses them, so its dependency list is an honest statement of what it builds on and each add-on’s lanes are forwarded from the client’s own features:
[dependencies]
capi_rs = "0.5"
capi_wire_model = { version = "0.5", features = ["derive"] }
capiw_ext_json = "0.5" # the `Json` marker, for a `Codec<Json>`
capi_auth_oauth2 = "0.5" # only when the client authenticates through OAuth2
Where the crates live
This is the distinction that will save you time in the source tree. Every repo
is a sibling checkout under one directory; capi_test_suite/repos.toml is the
registry that says what each is. There are three homes.
The capi_rs framework workspace holds the framework proper — the contracts and
their in-tree reference implementations, thirty crates:
- the protocol-generic core
capi_protocol(Protocol,Speaks,ApiClient,Duplex, and the transport vocabulary in itscarriermodule) over its root,capi_context(the request context, the budget, the send outcome) — the floor everything else stands on; - the anchor
capi_core— the HTTP core — and the facadecapi_rs; - the request pipeline —
capi_transport(the traversal engine and the seal),capi_middleware(the pipeline core: phases, members, verdicts),capip_http(theHttpmarker and the capability plane:Capability,Supports<Cap>, the envelopes, the follow-up channel),capi_authentication(the credential store, schemes, signers, presigning); - the runtime primitives
capi_time,capi_lock,capi_bytestream,capi_notification,capi_feature_state, and the vocabulary cratescapi_http_types(heads, headers, trailers, the canonical text format) andcapi_url; - rate limiting:
capi_rate_limit(the trait) andcapi_limiters(the backends); - the base implementations
capi_base_config,capi_base_decoders,capi_base_encoders,capi_base_interface; capiw_wire_headers(the header plane’s encoder);- the add-ons
capi_extras(runners, members, transfer stages) andcapi_cookies(with itscapi_cookies_macro); - the two client wrappers,
capi_firewall(egress policy) andcapi_debug_dump(development-time exchange dumps); - the macro crates
capi_deriveandcapi_macro_support, and the dev-only test harnesscapi_test_core.
The other six workspaces each own one concern:
capi_wire— the wire model:capi_wire_model(the traits,Codec,CodecError, theBinding),capi_wire_derive,capi_wire_value(the runtime value tree),capi_wire_datetime,capi_wire_decimal.capi_auth— the network auth flowscapi_auth_jwtandcapi_auth_oauth2.capi_engine— the sans-io engines and the family’s own transport: theConversation/Enginecontract and the per-lane drivers incapi_engine(which re-exports the core’s carrier traits at the paths its engines use), the wire enginescapip_http1andcapip_http2, the VOIP stackcapip_sip,capi_sdp,capip_stun,capip_rtp, the two protocol cratescapip_redisandcapip_voip(each a marker beside the session its exchange yields), andcapic_native_client, the engine-backed client. Everything here stands oncapi_protocol, the core in the Capi framework workspace.capi_graphql— the GraphQL runtime at the root,capi_graphql_macros(graphql_schema!,graphql!), and the host-only schema IRcapi_graphql_schema.capi_test_framework— replay testing, withcapi_test_macros.generic_llm_capi_rs— the OpenAI-compatible and Claude clients as one workspace.
Single-crate repos are the swappable, optional, and third-party-facing pieces — everything the orphan rule pushes outside, plus the capabilities a client opts into by name and the reference clients:
- Format contracts (
capiw_ext_*):capiw_ext_json(theJsonmarker),capiw_ext_urlencoded(UrlForm, andurlform_extensionbehind its off-defaultmodelfeature),capiw_ext_xml,capiw_ext_protobuf. - Codecs (
capiw_*):capiw_serde_json,capiw_urlencoded,capiw_quick_xml(std-locked),capiw_prost. Each carries its format’s conformance suite in its owntests/conformance.rs. - Transports (
capic_*):capic_reqwest,capic_ureq,capic_wasm_fetch(browser only),capic_reqwless(embeddedno_std). - Protocols and formats:
capi_websocket(RFC 6455 framing, theWebSocketcapability, correlated sessions),capi_grpc(unary, client-streaming, and server-streaming calls),capi_html_forms(the scrape-and-submit decoder),capi_http_batch(multipart/mixedbatch envelopes and OData changesets). - Auth add-ons: the signers
capi_auth_aws_sigv4,capi_auth_azure(Storage SharedKey),capi_auth_digest, andcapi_auth_google(API key, service-account JWT, OAuth2 bridged onto Google’s endpoints). - Content encodings (
capie_*):capie_gzip,capie_miniz_oxide(deflate),capie_brotli(br),capie_aws_chunked. Each shipsStreamCodecimplementations forBytestream::with_codec; an algorithm never lives in the crate that negotiates it. - Middleware add-ons (
capim_*):capim_x402, a payment runner — the prefix marks an add-on that plugs into the request pipeline, on either plane. - Clients (
*_capi_rs):grok,google_places,google_routes,aws_s3,azure_tables,icecast_connect,ringcentral,paychex,graphqlzero, and thegeneric_llmworkspace. Three private canaries prove a shape without being consumed:grpc_test(tonic interop),nostd_canary(bare-metal QEMU),runtime_canary(executor agnosticism). - Tooling:
capi_verify— thecapi_verifyplanner library and thecargo-capibinary, socargo capi verifyis the gate in any repo built on the framework;capi-extension-template, acargo generatetemplate for a new add-on; and the privatecapi_test_suite(the registry and the family sweep) andcapiw_conformance_tests(the multi-codec properties).
Adapters are bridges, and the family also ships its own. The
capic_*transports overreqwest,ureq, the browser’s Fetch API, andreqwless, and thecapiw_*codecs overserde_json,quick-xml, andprost, are thin bridges from an outside library to the framework’s traits — the orphan rule is why they are separate crates. Any library can implement those traits itself, the way crates ship optionalserdesupport. Beside the bridges sitscapic_native_client, the family’s own transport on thecapi_engineprotocol engines, with no third-party HTTP library in the exchange.
The wire-model chapter leans on the contract-vs-codec
split: a config depends on the tiny contract for the type (Codec<Json>)
and on a concrete codec only to build one. That is the split in crate form —
capiw_ext_json (the marker a signature names) vs. capiw_serde_json (the engine
that turns the bytes, swappable).
A layered view
The crates stack into tiers; the async and single-threaded axes must agree
along this stack (see Features and Targets), and a type’s
tier tells you roughly where to look for it.
graph TD
client["your client crate"] --> facade["capi_rs (facade + prelude)"]
client --> caps["Protocols & flows — capi_websocket, capi_grpc, capi_graphql, capi_html_forms, capi_http_batch, capi_auth_oauth2, capi_auth_jwt"]
client --> contracts["Format contracts — capiw_ext_json, capiw_ext_urlencoded, capiw_ext_xml, capiw_ext_protobuf"]
facade --> interface["Interface — capi_base_interface, capi_extras"]
facade --> config["Config — capi_base_config, capi_limiters"]
facade --> decoders["Decoders/Encoders — capi_base_decoders, capi_base_encoders"]
facade --> wrappers["Client wrappers — capi_firewall, capi_debug_dump"]
facade --> cookies["Cookies — capi_cookies"]
interface --> transport["Traversal & seal — capi_transport"]
decoders --> transport
transport --> core["Anchor — capi_core (Endpoint, ApiConfig, ResponseDecoder, errors)"]
config --> core
cookies --> core
caps --> core
core --> abstractions["Abstractions — capip_http, capi_middleware, capi_authentication, capi_rate_limit, capi_url"]
core --> wire["Wire model — capi_wire_model (+ derive, value, datetime, decimal)"]
contracts --> wire
wrappers --> abstractions
abstractions --> vocab["HTTP vocabulary — capi_http_types"]
abstractions --> floor["Protocol core — capi_protocol (+ capi_context)"]
vocab --> primitives["Primitives — capi_bytestream, capi_notification"]
floor --> primitives
primitives --> leaves["Leaves — capi_time, capi_lock, capi_feature_state"]
codecs["Codecs — capiw_serde_json / urlencoded / quick_xml / prost"] --> contracts
adapters["Transports — capic_reqwest / ureq / wasm_fetch / reqwless"] -.implements.-> abstractions
native["capic_native_client"] --> engine["Sans-io engines — capi_engine, capip_http1, capip_http2, …"]
native -.implements.-> abstractions
engine --> floor
Three things to read off it. The facade’s fan-out is the capi_rs
workspace; the protocol crates, the flows, and the format contracts hang off the
client, which is why their lanes are forwarded from the client’s features rather
than the facade’s. The dashed edges are the swap point: a transport implements
ApiClient and Speaks<Http> — which seats it as Supports<Http> — just as a
codec implements the wire model’s byte-engine trait; neither is on the mandatory
path, and both are chosen by the config and the interface. And capi_http_types
sits between the abstractions and the primitives: heads, headers, trailers, and
the canonical text format are the vocabulary the pipeline, the authentication
plane, and the recorder all speak.
The exhaustive crate-by-crate table is in the appendix.
Naming conventions
The prefixes are a quick decoder ring:
| Prefix | Role |
|---|---|
capi_rs | The public facade |
capi_core | The anchor: central traits + error taxonomy, re-exports sub-crates |
capi_protocol | The protocol-generic core: Protocol, Speaks, ApiClient, Duplex |
capi_* | Framework crates (transport, the pipeline, url, context, carrier, …) and the protocol/format add-ons (capi_websocket, capi_grpc, capi_graphql, capi_html_forms, capi_http_batch) |
capi_base_* | In-tree reference implementations of the core abstractions |
capi_auth_* | Auth flows and concrete signers (capi_authentication is the plane they plug into) |
capi_wire_* | The wire model workspace (model, derive, value, datetime, decimal) |
capiw_* | Wire formats: capiw_ext_* are contracts, the rest are codecs — plus capiw_wire_headers, the header plane’s encoder |
capic_* | Client adapters: the four bridges and the engine-backed capic_native_client |
capie_* | Content-encoding stream codecs |
capim_* | Middleware add-ons that plug into the request pipeline |
capip_* | The protocol class: a sans-io engine, a marker implementing capi_protocol’s Protocol with what a client needs to speak it, or both |
*_capi_rs | Client libraries built on the framework |
Which of those three a capip_ crate is varies: capip_http1 and
capip_http2 are engines, capip_http is the Http marker and its capability
plane, and capip_redis and capip_voip are both at once. The Protocol Core
and the Two Planes is the next chapter, and it has the rule
that decides where a marker lives.