confish
Official Rust SDK for confish — typed configuration, feeds, actions, and webhook verification.
- Async-first, built on
tokio+reqwest - Typed configuration via the standard
serde::Deserializegeneric —client.config().fetch::<MyConfig>().await? - Live feeds with declarative upserts and optional TTLs
- Long-running action consumer with
CancellationTokenand bounded concurrency - HMAC-SHA256 webhook verification that returns the parsed payload
Install
[]
= "0.2"
By default the SDK uses rustls. For native TLS, opt in:
= { = "0.2", = false, = ["native-tls"] }
Requires Rust 1.86+.
Quick start
use Client;
use Deserialize;
async
Reading and writing config
// GET /c/{env_id}
let config: MyConfig = client.config.fetch.await?;
// PATCH — only listed fields change
let updated: MyConfig = client.config.update.await?;
// PUT — replaces everything; omitted fields reset to defaults
let new_config: MyConfig = client.config.replace.await?;
Write access must be enabled in environment settings before
updateandreplacewill work.
Feeds
A feed holds your environment's live state — active jobs, crawl results, open incidents — keyed by an external ID you choose. client.feed(slug) returns a bound handle; construction performs no HTTP.
use FeedItem;
use ;
use Duration;
let crawls = client.feed;
// PUT — create-or-replace by external ID, with an optional TTL
crawls.set.await?;
// GET — live (non-expired) items, newest first
let items: = crawls.list.await?;
// DELETE — idempotent; deleting an item that's already gone succeeds
crawls.delete.await?;
set is declarative: the item's data becomes exactly what you send, and the TTL becomes exactly what you pass. Passing None for ttl makes the item permanent — it clears any TTL set by a previous call. TTLs are sent as whole seconds and must be between 1 second and 30 days; external_id must be 255 characters or fewer.
Replacing the whole feed
replace is a collection PUT built for sync-style cron jobs that push their full dataset in one request — the feed becomes exactly the items you send. Existing external IDs update in place, new ones are created, and anything absent is deleted; an empty slice clears the feed. It's all-or-nothing: duplicate external IDs, payloads over the plan's item cap, or any schema-invalid item are rejected with Error::Validation and nothing is written.
use FeedItemInput;
use json;
use Duration;
let result = client.feed.replace.await?;
println!;
An unknown feed slug returns Error::NotFound. A full feed or an item that fails the feed's schema returns Error::Validation.
Logging
use LogLevel;
use json;
client.logs.info.await?;
client.logs.error.await?;
// Or with an explicit level:
let log_id = client.logs.write.await?;
Levels (the full RFC 5424 set): Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency. Because the levels follow RFC 5424 (syslog), they map cleanly onto the level vocabularies of the log and tracing crates.
Actions
The action consumer polls for pending actions, acknowledges them, runs your handler, and reports completion or failure — including idempotent skip if another consumer claimed the action first.
use ;
use json;
use Duration;
use CancellationToken;
let cancel = new;
let cancel_clone = cancel.clone;
spawn;
client
.actions
.consume
.await?;
What happens automatically:
- Returning
Ok(Some(value))completes the action withvalueasresult. - Returning
Ok(None)completes with no result. - Returning
Err(_)fails the action with{"error": <message>}. - A
409 Conflicton ack is silently skipped — safe to run multiple consumers. - Cancelling the token halts new work and waits for in-flight handlers to finalize.
- After 3 consecutive empty polls the loop doubles its sleep up to
max_poll_interval, resetting topoll_intervalthe moment any action is processed. Idle consumers make ~240 requests/hour by default.
You can also drive the lifecycle manually:
let actions = client.actions.list.await?;
client.actions.ack.await?;
client.actions.progress.await?;
client.actions.complete.await?;
client.actions.fail.await?;
To extract typed params:
if let Some = action.params.clone
Webhook verification
verify checks the signature and parses the payload in one operation — the Stripe pattern. On success you get the parsed Payload; on failure a WebhookError that says why (InvalidSignature vs TimestampOutsideTolerance), so the failure mode can't be ignored.
use ;
async
verify uses constant-time comparison and rejects timestamps older than 5 minutes by default. Pass VerifyOptions { tolerance: Duration::ZERO, .. } to disable. Always pass the raw, unparsed body — re-serializing parsed JSON breaks verification.
Errors
use Error;
match client.config..await
Error::is_conflict() is a convenience for action consumers.
By default the client retries 429 (honoring Retry-After) and 5xx responses up to twice. Tune via the builder.
Builder options
let client = builder
.base_url // override for self-hosted
.user_agent
.max_retries
.max_retry_delay
.http_client // inject your own
.build?;
License
MIT