About SmooAI
SmooAI is an AI-powered platform for helping businesses multiply their customer, employee, and developer experience.
Learn more on smoo.ai
SmooAI Packages
Check out other SmooAI packages at smoo.ai/open-source
About smooai-config (Rust)
Type-safe config, secrets, and feature flags for Rust - Same schema, same keys, same source of truth as your TypeScript, Python, Go, and .NET services. All strongly typed, all async.
Rust Crate
Rust port of @smooai/config. Derive JsonSchema on your own Rust structs, generate the exact schema every other service in your stack reads, and resolve values through a cached async client.
Note: the
smooai-configCLI (push / pull / list / set / diff / login) is TypeScript-only. The schema is authored in TS and pushed via the CLI; this Rust crate only reads values at runtime. If you're on a Rust-only team and need the CLI, install it via Node:pnpm add -g @smooai/config(ornpm i -g @smooai/config).
What you get
- Three tiers, one schema - public config, secrets, and feature flags as three Rust structs with
#[derive(JsonSchema)]. - Strongly-typed, idiomatic Rust -
define_config_typed::<Public, Secret, Flags>()turns your structs into the schema every other service reads. - Any environment, any key - same API for
development,staging,productionwith per-stage overrides. - Cross-language source of truth - the same schema lives in TypeScript, Python, Go, and .NET services.
- Zero-config client setup -
ConfigClient::from_env()picks upSMOOAI_CONFIG_*and goes. - Async + cached - fetched values stay in-process between calls; invalidate on demand or set a TTL.
Install
Add to your Cargo.toml:
[]
= "0.1"
or using cargo:
All Language Packages
| Language | Package | Install |
|---|---|---|
| TypeScript | @smooai/config |
pnpm add @smooai/config |
| Python | smooai-config |
pip install smooai-config |
| Rust | smooai-config |
cargo add smooai-config |
| Go | github.com/SmooAI/config/go/config |
go get github.com/SmooAI/config/go/config |
| .NET | SmooAI.Config |
dotnet add package SmooAI.Config |
Usage
Define Configuration Schemas with Native Rust Types
The preferred way to define configuration is with Rust structs that derive JsonSchema. Use EmptySchema for tiers that have no configuration values:
use ;
use JsonSchema;
use ;
// Generates JSON Schema from your Rust types and validates cross-language compatibility
let config = ;
println!;
Define Configuration Schemas from Raw JSON Schema
Alternatively, pass raw JSON Schema values directly:
use define_config;
let public_schema = json!;
let config = define_config;
Runtime Client - Fetch Values from Server
The ConfigClient is async and uses reqwest under the hood. Before each call it mints a short-lived JWT via the OAuth2 client_credentials grant against {auth_url}/token (cached and auto-refreshed via [TokenProvider]). Fetched values are cached locally until invalidate_cache is called or a TTL expires. Note: SDK versions prior to SMOODEV-975 sent the raw API key as Bearer — the backend rejects that flow with 401.
use ConfigClient;
async
Caching
Cache TTL can be configured with set_cache_ttl. By default the cache never expires (manual invalidation only):
use ConfigClient;
use Duration;
let mut client = new;
// Set a 5-minute TTL
client.set_cache_ttl;
// Fetched from server and cached
let value = client.get_value.await?;
// Served from cache
let value = client.get_value.await?;
// Invalidate all cached values
client.invalidate_cache;
// Invalidate cached values for one environment
client.invalidate_cache_for_environment;
Local Configuration Manager
For local development or offline environments, LocalConfigManager loads configuration from .smooai-config/ files and environment variables:
use LocalConfigManager;
let manager = new?;
// Fetch values from local file config + env vars
let api_url = manager.get_public_config?;
let db_url = manager.get_secret_config?;
let new_ui = manager.get_feature_flag?;
Baked Runtime — zero-network cold starts
For Lambda / ECS / long-lived services, bake every public + secret value into an AES-256-GCM blob at deploy time and decrypt it at cold start. build_config_runtime decrypts the blob and seeds the manager's merged config map, so public/secret reads resolve from in-memory cache with no HTTP round-trip. Feature flags are skipped (the baker drops them) so they stay live-fetched.
use ;
async
Bake the bundle at deploy time:
use HashSet;
use ;
async
Blob env vars
| Variable | Value |
|---|---|
SMOO_CONFIG_KEY_FILE |
Absolute path to the .enc bundle on disk |
SMOO_CONFIG_KEY |
Base64-encoded 32-byte AES-256 key |
Without both set, build_config_runtime returns a plain ConfigManager so dev machines without a baked blob still work — the API stays uniform either way.
The blob format is nonce (12 bytes) || ciphertext || authTag (16 bytes) — wire-identical to the TypeScript, Python, Go, and .NET runtimes. A blob baked in any language decrypts in any other.
Container / Runtime Mode
For long-lived containers (EKS/ECS) the baked blob is the wrong default — when the per-build blob key isn't delivered to the pod, resolution silently falls through to the (absent) file tier and returns an absent value for a required secret (the SMOODEV-1478 CrashLoop outage). Container mode makes the HTTP config API the first-class, fail-loud path: a missing required value is an immediate, typed error (ConfigKeyUnresolvedError), never a silent absent value.
This is the Rust implementation of the five-language parity contract. The env contract, mode selection, fail-loud semantics, caching, and the Kubernetes / External Secrets Operator recipe are documented once in the shared, language-agnostic guide: docs/Container-Runtime-Mode.md.
use ;
use define_config;
let schema = define_config;
// Validates the container env contract (SMOOAI_CONFIG_API_URL / CLIENT_ID /
// CLIENT_SECRET / ORG_ID / ENV), mints an M2M OAuth token, and does an initial
// fetch — auth/network/missing-env failures surface HERE, at startup, not on
// first read. Missing/blank required env => Err(ConfigError::Bootstrap) listing
// exactly which vars are missing.
let handle = init_container_config
.await?;
// Fail-loud read: a required key that resolves absent returns
// Err(ConfigError::KeyUnresolved { key, env, tried_tiers }) — never Ok(None).
let stripe_key = handle.secret_config.get.await?;
// Sync read off the cache mirror (same fail-loud contract).
let api_url = handle.public_config.get_sync?;
// Non-failing status for a Kubernetes readiness/liveness probe (/healthz/config):
// Healthy once the initial fetch succeeded; serves last-good within the 30s cache
// TTL; Unhealthy past hard-expiry on a sustained refresh failure.
match handle.health
Mode selection is available as select_mode(...) for callers that need to branch between container mode and the existing blob/file chain (see §2 of the shared doc):
use ;
// Reads SMOOAI_CONFIG_MODE / M2M creds / blob+file presence from the env.
if select_mode == Container
Defaults match every other SDK: DEFAULT_CACHE_TTL = 30s, DEFAULT_TOKEN_REFRESH_BUFFER_SECONDS = 60. On a 401 the token is invalidated and the request retried once.
Environment Variables
All clients read from the same set of environment variables:
| Variable | Description | Required |
|---|---|---|
SMOOAI_CONFIG_API_URL |
Base URL of the config API | Yes |
SMOOAI_CONFIG_CLIENT_ID |
OAuth2 client ID | Yes |
SMOOAI_CONFIG_CLIENT_SECRET |
OAuth2 client secret (legacy SMOOAI_CONFIG_API_KEY accepted as deprecated alias) |
Yes |
SMOOAI_CONFIG_AUTH_URL |
OAuth issuer base URL (defaults to https://auth.smoo.ai; legacy SMOOAI_AUTH_URL accepted) |
No |
SMOOAI_CONFIG_ORG_ID |
Organization ID | Yes |
SMOOAI_CONFIG_ENV |
Default environment name (defaults to "development") |
No |
Set these in your environment and the client will use them automatically:
Configuration Tiers
| Tier | Purpose | Examples |
|---|---|---|
| Public | Client-visible settings | API URLs, feature toggles, UI config |
| Secret | Server-side only | Database URLs, API keys, JWT secrets |
| Feature Flags | Runtime toggles | A/B tests, gradual rollouts, beta access |
Common errors
get_public_config / get_secret_config returning Ok(None) for a known key
If you read a key that wasn't declared in the schema your service was built against, the manager's merged map has no entry and lookups return Ok(None). The common cause is a schema rebase mismatch — the consumer was built against an older schema.json than what's in your config repo. Re-run the schema generator to pick up the new keys, or add the missing key to your schema.
Built With
- Rust 2021 Edition - Memory safety and performance
- schemars - JSON Schema generation from Rust types
- serde / serde_json - JSON serialization
- reqwest - Async HTTP client
- tokio - Async runtime
Development
Running tests
Building
Linting and Formatting
Related Packages
- @smooai/config - TypeScript/JavaScript version
- smooai-config (Python) - Python version
- smooai-config (Rust) - This package
github.com/SmooAI/config/go/config- Go version- SmooAI.Config (NuGet) - .NET version
- SmooAI/config - GitHub repository
Contact
Brent Rager
Smoo Github: https://github.com/SmooAI
License
MIT © SmooAI