Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. 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 GetUser only because Endpoint is in capi_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, …).
  2. 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.
  3. Granular dependencies. A no_std client can depend on capi_core plus one adapter and skip the rest, keeping build times and binary size down.
  4. The facade collapses it back. capi_rs re-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 its carrier module) 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 facade capi_rs;
  • the request pipeline — capi_transport (the traversal engine and the seal), capi_middleware (the pipeline core: phases, members, verdicts), capip_http (the Http marker 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 crates capi_http_types (heads, headers, trailers, the canonical text format) and capi_url;
  • rate limiting: capi_rate_limit (the trait) and capi_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) and capi_cookies (with its capi_cookies_macro);
  • the two client wrappers, capi_firewall (egress policy) and capi_debug_dump (development-time exchange dumps);
  • the macro crates capi_derive and capi_macro_support, and the dev-only test harness capi_test_core.

The other six workspaces each own one concern:

  • capi_wire — the wire model: capi_wire_model (the traits, Codec, CodecError, the Binding), capi_wire_derive, capi_wire_value (the runtime value tree), capi_wire_datetime, capi_wire_decimal.
  • capi_auth — the network auth flows capi_auth_jwt and capi_auth_oauth2.
  • capi_engine — the sans-io engines and the family’s own transport: the Conversation/Engine contract and the per-lane drivers in capi_engine (which re-exports the core’s carrier traits at the paths its engines use), the wire engines capip_http1 and capip_http2, the VOIP stack capip_sip, capi_sdp, capip_stun, capip_rtp, the two protocol crates capip_redis and capip_voip (each a marker beside the session its exchange yields), and capic_native_client, the engine-backed client. Everything here stands on capi_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 IR capi_graphql_schema.
  • capi_test_framework — replay testing, with capi_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 (the Json marker), capiw_ext_urlencoded (UrlForm, and urlform_extension behind its off-default model feature), 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 own tests/conformance.rs.
  • Transports (capic_*): capic_reqwest, capic_ureq, capic_wasm_fetch (browser only), capic_reqwless (embedded no_std).
  • Protocols and formats: capi_websocket (RFC 6455 framing, the WebSocket capability, correlated sessions), capi_grpc (unary, client-streaming, and server-streaming calls), capi_html_forms (the scrape-and-submit decoder), capi_http_batch (multipart/mixed batch envelopes and OData changesets).
  • Auth add-ons: the signers capi_auth_aws_sigv4, capi_auth_azure (Storage SharedKey), capi_auth_digest, and capi_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 ships StreamCodec implementations for Bytestream::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 the generic_llm workspace. 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 — the capi_verify planner library and the cargo-capi binary, so cargo capi verify is the gate in any repo built on the framework; capi-extension-template, a cargo generate template for a new add-on; and the private capi_test_suite (the registry and the family sweep) and capiw_conformance_tests (the multi-codec properties).

Adapters are bridges, and the family also ships its own. The capic_* transports over reqwest, ureq, the browser’s Fetch API, and reqwless, and the capiw_* codecs over serde_json, quick-xml, and prost, 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 optional serde support. Beside the bridges sits capic_native_client, the family’s own transport on the capi_engine protocol 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:

PrefixRole
capi_rsThe public facade
capi_coreThe anchor: central traits + error taxonomy, re-exports sub-crates
capi_protocolThe 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_rsClient 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.