treetop-client
A Rust client library for Treetop policy authorization servers.
Treetop is a Cedar-based policy evaluation service. This client provides a typed, async API for evaluating authorization requests, managing policies, and querying server status.
Current contract
Version 0.1.0 targets the coordinated REST 0.1.0 contract. Early releases prioritize correctness over compatibility: deprecated APIs, legacy endpoint methods, omitted metadata defaults, and old-server matrices are removed. See MIGRATION.md. CI runs the full endpoint suite against an immutable REST 0.1.0 release image.
Features
- Type-driven design -- strongly typed request/response types with serde, wire-compatible with the Treetop REST API
- Capability-safe uploads -- upload methods exist only on
Client<CanUpload>values built with a validated token - Connection pooling -- built on reqwest with configurable pool sizes and idle timeouts
- Secure token handling -- upload tokens backed by
SecretString(zeroized on drop, redacted in Debug output) - Validated construction -- fallible constructors preserve Cedar, entity, attribute, context, and header invariants
- TLS by default -- uses rustls without an OpenSSL/system-TLS dependency, with optional custom root certificates
- Bounded I/O -- request and successful-response bodies are capped at 16 MiB by default, with configurable limits
- Fluent endpoint calls -- typed detail and output transitions for authorization and user-policy queries
- Batch authorization -- evaluate multiple authorization requests in a single API call
- Correlation IDs -- clone-with-override pattern for request tracing without shared mutable state
- Schema management -- download and upload Cedar schema data alongside policies
- Operational endpoints -- typed liveness/readiness checks and access to the generated OpenAPI document
Installation
Add to your Cargo.toml:
[]
= "0.1.0"
= { = "1", = ["macros", "rt-multi-thread"] }
Quick start
use ;
async
Usage
Client configuration
use Duration;
use ;
let client = builder
.connect_timeout
.request_timeout
.pool_idle_timeout
.pool_max_idle_per_host
.max_request_bytes
.max_response_bytes
.request_limits
.upload_token
.build?;
Without .upload_token(...), build() returns Client<ReadOnly> and upload methods are not
available. Adding the token transitions the builder and resulting client to CanUpload; read and
authorization methods remain available in both states.
Upload tokens require HTTPS unless the destination is loopback. For an explicitly accepted
plaintext development server, opt in with .danger_allow_insecure_uploads(true). The default
HTTP client also rejects redirects so a token cannot be forwarded to a redirect target. A custom
reqwest client bypasses that redirect policy, so configure its policy deliberately.
For custom TLS configuration:
use Client;
let client = builder
.add_root_certificate
.build?;
Or bring your own pre-configured reqwest client:
let client = builder
.with_reqwest_client
.build?;
Correlation IDs
Correlation IDs are managed via a clone-with-override pattern. The cloned client shares the same connection pool:
let traced = client.with_correlation_id?;
traced.authorize.await?; // sends x-correlation-id header
// Original client is unaffected
client.authorize.await?; // no correlation header
// Fluent endpoint calls can override correlation without cloning the client
client
.authorization
.correlation_id?
.send
.await?;
Authorization
Request fields are private and exposed through read-only accessors. Constructors and fluent
setters validate before returning a value, while batch- and server-dependent invariants are checked
before transport. Duplicate request IDs are rejected, and response IDs, indices, ordering, counts,
policy versions, and decisions are checked against the submitted batch. AttrValue::ip() always
validates its IP/CIDR value.
Single check
use ;
let allowed = client
.is_allowed
.await?;
Batch authorization
use ;
let batch = new
.add_request
.add_request_with_id?;
let response = client.authorization.send.await?;
println!;
// Look up a result by client-provided ID
if let Some = response.find_by_id
Detailed authorization (includes matching policies)
let response = client.authorization.detailed.send.await?;
Resources with attributes
use ;
let resource = new?
.with_attr?
.with_attr?
.with_attr?
.with_attr?;
Namespaced types
Users, groups, and actions support Cedar namespaces:
use ;
let user = new?
.with_namespace?
.with_group_names?;
let action = new?
.with_namespace?;
let group = new?
.with_namespace?;
Policy management
// Download policies as structured data
let download = client.get_policies.await?;
// Download policies as raw Cedar DSL
let cedar_text = client.get_policies_raw.await?;
// Upload policies (requires upload token)
let metadata = client
.upload_policies_raw
.await?;
// List policies for a specific user
let user_policies = client
.user_policies?
.group?
.namespace?
.send
.await?;
Schema management
// Download schema as structured metadata
let schema = client.get_schema.await?;
// Download schema as raw Cedar schema JSON
let raw_schema = client.get_schema_raw.await?;
// Upload schema (requires upload token)
let metadata = client
.upload_schema_raw
.await?;
Server status
let version = client.version.await?;
println!;
let status = client.status.await?;
println!;
println!;
if let Some = &status.policy_configuration.policies.source
The canonical operational probes and generated OpenAPI document are available directly:
client.livez.await?;
if client.readyz.await?
let openapi = client.openapi.await?;
println!;
Request context
Request-scoped context is serialized on the wire via AuthRequest.context and evaluated by
treetop-rest 0.1.0. The client automatically enforces RequestLimits::default() before
transport; configure limits reported by a differently configured server with
ClientBuilder::request_limits().
use HashMap;
use ;
let mut context = new;
context.insert;
let request = new
.with_context?;
let batch = from_auth_requests?;
let response = client.authorization.send.await?;
Inspect status.request_context if you need to know whether the server runtime is currently schema-backed or
running in permissive fallback mode. Uploading a schema via upload_schema_raw() or upload_schema_json() lets
you verify the schema-backed path explicitly.
Prometheus metrics
let metrics_text = client.metrics.await?;
Error handling
All methods return treetop_client::Result<T>, which uses TreetopError:
use TreetopError;
match client.livez.await
License
MIT
Releasing
Stable tags drive the crates.io and GitHub release workflow. See RELEASING.md for the release process.