cratestack-client-rust 0.7.7

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
Documentation

cratestack-client-rust

Rust HTTP client runtime for CrateStack services.

Overview

cratestack-client-rust provides the typed client runtime that include_client_schema! builds its generated client::Client surface on top of. It owns the HTTP transport, codec negotiation, request authorization hook, and optional offline state journaling.

The CBOR and JSON codecs are re-exported as CborCodec and JsonCodec.

Installation

[dependencies]
cratestack-client-rust = "0.6.7"
tokio = { version = "1", features = ["rt-multi-thread"] }
url = "2"

Usage

use cratestack::include_client_schema;
use cratestack_client_rust::{CborCodec, ClientConfig, CratestackClient};

include_client_schema!("../schemas/api.cstack");

let base_url = url::Url::parse("https://api.example.com")?;
let runtime = CratestackClient::new(ClientConfig::new(base_url), CborCodec);
let client = cratestack_schema::client::Client::new(runtime);

This is the REST transport. For a schema declaring transport rpc, the generated cratestack_schema::rpc::Client is built on top of RpcClient instead — see RPC Transport below.

RPC Transport

For schemas declaring transport rpc, include_client_schema! generates an RPC client built on RpcClient rather than CratestackClient. RpcClient shares its transport, codec, and state store with the REST client (both can be used side-by-side against the same server) but dispatches unary calls to POST /rpc/{op_id} and supports request batching via BatchBuilder/BatchHandle/BatchableCall, plus streamed responses via RpcStream. See docs/design/rpc-transport.md in the repo for the wire-format spec.

gRPC Client

Enable the grpc feature for a native tonic-based gRPC client runtime (ticket #209), the gRPC sibling of the REST and RPC transports above:

[dependencies]
cratestack-client-rust = { version = "0.6.7", features = ["grpc"] }

The feature is off by default because it pulls in tonic (and transitively prost, h2, tower) — a REST/RPC-only consumer never pays for it. include_client_schema! generates a cratestack_schema::grpc::Client<T = tonic::transport::Channel> on top of cratestack_client_rust::grpc::CratestackGrpcClient<T>, mirroring tonic-build's own generated client shape:

use cratestack::include_client_schema;

include_client_schema!("../schemas/api.cstack");

let mut client = cratestack_schema::grpc::Client::connect("http://127.0.0.1:50051").await?;
let widget = client.widgets().get(&widget_id).await?;

CratestackGrpcClient::with_request_authorizer attaches the same RequestAuthorizer convention the REST/RPC clients use, so a schema author configures auth once regardless of transport — the canonical string is derived from the call's unframed prost-encoded bytes (see cratestack_client_rust::grpc::canonical). Errors surface as GrpcClientError, which wraps tonic::Status directly rather than decoding a body (a gRPC error already arrives as a structured status the server derived from the same CoolError REST/RPC use).

Codecs

use cratestack_client_rust::{CborCodec, JsonCodec};

let cbor_client = CratestackClient::new(config.clone(), CborCodec);
let json_client = CratestackClient::new(config, JsonCodec);

Request Authorization

with_request_authorizer attaches an implementation of RequestAuthorizer that returns extra headers per call. The trait gets a canonical-request string the implementer can sign. authorize is async (issue #453), so credential providers that need to make a network call — refreshing a cached OAuth2 token, for instance — can do so directly instead of pre-fetching or blocking on the runtime:

use std::sync::Arc;
use cratestack_client_rust::{AuthorizationRequest, ClientError, RequestAuthorizer};

struct HmacAuthorizer { key: Vec<u8> }

#[async_trait::async_trait]
impl RequestAuthorizer for HmacAuthorizer {
    async fn authorize(
        &self,
        request: &AuthorizationRequest,
    ) -> Result<Vec<(String, String)>, ClientError> {
        let sig = sign(&self.key, &request.canonical_request_string());
        Ok(vec![(
            "authorization".to_owned(),
            format!("Signature {}", hex::encode(sig)),
        )])
    }
}

let client = runtime.with_request_authorizer(Arc::new(HmacAuthorizer { key }));

State Persistence

Journal requests for replay or offline recovery. The bundled implementations are InMemoryStateStore and JsonFileStateStore; the trait is ClientStateStore.

use std::sync::Arc;
use cratestack_client_rust::{ClientStateStore, JsonFileStateStore};

let store: Arc<dyn ClientStateStore> = Arc::new(JsonFileStateStore::new("./client_state.json"));
let runtime = runtime.with_state_store(store);

with_optional_state_store(None) is a no-op convenience for configuration-driven setup.

For a Redis-backed store, see cratestack-client-store-redis. For a SQLite-backed store, see cratestack-client-store-sqlite.

See Also

  • Client Runtime
  • Transport Architecture
  • docs/design/rpc-transport.md — RPC wire-format spec
  • cratestack-codec-cbor — CBOR codec
  • cratestack-codec-json — JSON codec
  • cratestack-client-store-redis — Redis-backed ClientStateStore
  • cratestack-client-store-sqlite — SQLite-backed ClientStateStore

License

MIT