SDK Overview
SecretSpec ships SDKs for Rust, Python, Go, Ruby, Node.js/TypeScript, and
Haskell. They all resolve secrets from the same declarative secretspec.toml,
and they all behave identically, because they share one resolver.
One resolver, thin clients
Section titled “One resolver, thin clients”Resolution (providers, fallback chains, profiles, generation, as_path
materialization) lives in a single Rust core. Each SDK is a thin client over
that core rather than a reimplementation:
- Rust uses the library directly, with a compile-time derive macro for strongly-typed access.
- Python (cffi), Go (purego), and Ruby (Fiddle) load the
secretspec-ffiC ABI and exchange a small JSON request/response with it. - Haskell links the same C ABI at build time via the GHC FFI.
- Node.js/TypeScript uses a napi-rs native addon that embeds the same resolver.
Because resolution happens in one place, every provider, chain, profile, and generator works the same in every language, and a new provider added to the core is immediately available everywhere with no per-SDK change. A cross-language conformance suite asserts that all the SDKs reduce the same inputs to the same result.
The runtime API
Section titled “The runtime API”Each SDK mirrors the Rust derive crate’s vocabulary: a builder that takes a
provider, profile, and an access reason, and a load/resolve that returns the
resolved secrets plus the provider and profile used. A missing required secret
is a typed error, distinct from a transport failure (which carries a stable
kind). Secrets exposed as_path come back as a readable file path.
from secretspec import SecretSpec
resolved = SecretSpec.builder().with_provider("keyring://").with_reason("boot").load()print(resolved.secrets["DATABASE_URL"].get)See each language’s page for the idiomatic spelling: Rust, Python, Go, Ruby, Node.js, and Haskell.
Typed access
Section titled “Typed access”Beyond the Rust derive macro, typed accessors for the other languages are
generated from the manifest. secretspec schema emits a JSON Schema for the
secret shape; quicktype turns it into an idiomatic type
and deserializer for any language, which you build from the SDK’s fields()
map:
secretspec schema | quicktype -s schema --top-level SecretSpec --lang <language>This keeps the per-language surface tiny: the SDK only provides fields(), and
quicktype owns the type generation.
Distribution
Section titled “Distribution”The SDKs are designed to install with no native build: the C ABI library is
bundled in the Python wheel and the Ruby gem, embedded in the Go module, and
built as a napi-rs addon for Node (prebuilt per-platform npm packages are a
follow-up). The native library is otherwise discovered from the
SECRETSPEC_FFI_LIB environment variable or a Cargo target directory, which
is how it works from a source checkout.
The Haskell SDK is the exception: it links the C ABI at build time, so the
secretspec-ffi cdylib must be on the linker path (--extra-lib-dirs) and the
runtime loader path when building and running it.