SDK requirements
A docuconf SDK has one job: let an app declare its configuration in the way its ecosystem already does, and add what is missing for a platform contract. There are 16 SDKs today: Go, TypeScript (T3 Env and NestJS config), .NET, Python, Ruby, Java, Kotlin, Rust, Swift, Elixir, Gleam, C++, PHP (Laravel and Symfony) and COBOL. Each has a Get started page.
Build on the host library
Every language already has a configuration library teams trust. An SDK must extend it, not replace it. The host keeps loading, parsing and binding; its users keep its API, docs and idioms. The SDK adds only:
- Metadata the host cannot express — descriptions,
secret, constraints with no host equivalent. - Validation the host does not do, run after the host has parsed.
- Contract export, from the same declaration, so the contract cannot drift from the code.
Each SDK sets the contract's encoding fields to whatever its host parses, so the host never needs a custom parser for platform-rendered values. Where the host conflicts with a rule in the spec (for example, empty-string handling), the SDK adapts the host with a pre-check.
Cloud-native by default
Every SDK must work however the configuration arrives:
- Environment variables set in the pod spec — literals and Kubernetes references.
- Values injected at runtime — Bank-Vaults'
vault-env, wrappers such asop runordoppler run, operators and webhooks. The SDK reads the process environment when the process starts, after injection, and validates injected values exactly like any other. It never resolves secret references itself and never reads configuration at build time. - Files mounted or written at runtime — Secrets, ConfigMaps, CSI volumes, cert-manager certificates, Vault Agent templates — checked at boot and, with
reload: watch, reloaded when they rotate. - Config-file overlays on hosts that layer files — a platform-mounted
appsettings.Production.json,application.ymlorConfig.toml, loaded between the baked-in files and the environment.
See Inputs for how the contract and the platform describe each case.
Requirements
A conforming SDK must:
- 1Idiomatic declaration. Cover every variable type, file type and field, in the host library’s own style.
- 2Validate the declaration. At definition time: name format, description length, details not blank and at most 4000 characters, default against constraints, no default on required, RE2-only patterns, no watch where reload is unsupported.
- 3Export a conforming contract. Deterministic, sorted by name, canonical durations (1h30m), encodings set to what the host parses, narrow integer ranges exported as min/max, full-match patterns anchored as ^(?:p)$.
- 4Load from the process environment, at process start. Read the real environment when the process starts, after any injection (Bank-Vaults, Vault Agent, wrappers such as op run), and validate injected values exactly like any other. Never resolve secret references itself, and never read configuration at build time. A .env file is opt-in for development, and real variables override it.
- 5Support config-file overlays (hosts that layer files). Declare overlay files in the contract, load them in the documented order (base file < profile file < platform overlay < environment variables), and reload them when declared watch. Applies to .NET, Spring, Rails, figment and Hoplite; other hosts reject overlays at declaration time.
- 6Report every violation at boot. All together, each with a stable error code, never printing a secret value; also to /dev/termination-log. Length limits count characters (Unicode code points), and a value outside them is out_of_range.
- 7Expose typed values. A struct, class or inferred type — never a string map.
- 8Check every file input at boot. Existence, readability and size; config files parse and bind; TLS key match, validity window, minRemaining, DNS names, key algorithm and chain; CA bundle count; keystore opens; text constraints.
- 9Honour reload: watch. Reload watched files (Kubernetes swaps a symlink), or reject watch at declaration time.
- 10Ignore undeclared variables. HOSTNAME, KUBERNETES_* and the like. The unknown-name check applies only to platform values.
- 11Support DOCUCONF_FILE_ROOT. A directory prepended to every absolute file path, for local runs and tests.
- 12Offer a contract-first mode. Validate an environment against a contract.json with no in-language declaration, parsing every list and duration encoding. The conformance runner uses it.
- 13Pass the conformance suite. Every case in docuconf-go’s conformance/cases.json, run through the contract-first mode: the same typed values or the same error codes in every language.
And should:
- 1Export details. From the language’s natural doc location, beside the description. docuconf docs generates the documentation from the contract; an SDK needs no generator of its own.
- 2Framework integration. A Railtie, ValidateOnStart in .NET, a Next.js or NestJS adapter, a Spring auto-configuration.
- 3Warn on feature-flag names. Variables matching ^(FF|FEATURE|FEATURE_FLAG|ENABLE)_ are probably flags.
- 4Detect unresolved injection references. Fail with invalid_type when a secret still holds an injector reference (vault:, op://, ref+) because the injector did not run.
- 5Warn on Unicode regex classes. Where the host’s \d, \w, \s, \b are Unicode-aware but RE2’s are ASCII.
Boot-time error codes
Every violation found at boot is reported with one of these codes, so platforms and alerting can act on them the same way in every language. Length limits use out_of_range; an expired certificate, a disallowed key algorithm or a broken chain is certificate_invalid; a CA bundle with too few certificates is file_malformed.
| Code | Meaning |
|---|---|
missing_required | A required variable or file input has no value. |
invalid_type | The value does not parse as its type. |
out_of_range | Outside min/max or the 64-bit range, a list item outside itemMin/itemMax, or a string, url, json value, list item or text file outside its length limits (counted in characters). |
pattern_mismatch | A string or text file does not match its pattern. |
not_in_enum | Not one of the enum’s values. |
invalid_scheme | A URL with a scheme not in schemes. |
too_few_items | A list shorter than minItems. |
too_many_items | A list longer than maxItems. |
file_missing | A file input’s path does not exist. |
file_unreadable | The path exists but cannot be read. |
file_too_large | Larger than maxSize. |
file_malformed | Does not parse in its format, or a CA bundle with too few certificates. |
schema_mismatch | A config file or json value does not match its schema. |
certificate_invalid | Unparseable, expired, not yet valid, disallowed key algorithm, or a broken chain. |
certificate_expiring | Valid, but with less than minRemaining left. |
certificate_name_mismatch | Does not cover every name in dnsNames. |
key_mismatch | The private key does not match the certificate. |
keystore_unreadable | The keystore does not open with its password. |
The SDKs today
| SDK | Host library | Package | Variable types | Lists | Durations | Config files | Keystores | reload: watch | Profiles | Export | Conformance | Example |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Go | caarlos0/env v11 | github.com/docuconf/docuconf-go | all 9 | csv | go | json, yaml | PKCS#12 | all file types | — | docuconf export -pkg -type | 134 of 134 | net/http |
| TypeScript (T3 Env) | T3 Env + Zod 4 | @docuconf/t3 (npm) | all 9 | csv | go | json, yaml | PKCS#12 (JKS: format check) | all file types | — | npx docuconf-t3 export src/env.ts | 131 of 134 (skips int64, json-schema) | T3 Env |
| TypeScript (NestJS) | @nestjs/config + class-validator | @docuconf/nestjs (npm) | all 9 | csv | go | json, yaml | PKCS#12 (JKS: format check) | all file types | — | npx docuconf-nestjs export src/orders.config.ts | 131 of 134 (skips int64, json-schema) | NestJS |
| .NET | Options pattern + appsettings | Docuconf.Options (NuGet) | all 9 | indexed | timespan | json | PKCS#12 | tls, caBundle, keystore, binary | appsettings.{Environment}.json | dotnet app.dll docuconf export | 132 of 134 (skips json-schema) | ASP.NET Core |
| Python | pydantic-settings 2 | docuconf-pydantic (PyPI) | all 9 | json (csv opt-in) | iso8601 | json, yaml, toml | PKCS#12 | all file types | — | docuconf export mod:Settings | 134 of 134 (json-schema with the jsonschema extra) | http.server |
| Ruby | anyway_config 2 | docuconf-anyway (RubyGems) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12 (JKS: format check) | all file types | Rails YAML (RAILS_ENV) | rails docuconf:export | 134 of 134 | Rack |
| Java (Spring Boot) | Spring Boot 3 / 4 @ConfigurationProperties | dev.docuconf:docuconf-spring (Maven) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12, JKS | all file types | application-{profile}.yml | annotation processor at compile time | 134 of 134 | Spring Boot |
| Kotlin | Hoplite 3 | dev.docuconf:docuconf-hoplite (Maven) | all 9 | csv | iso8601 | json, yaml, toml | PKCS#12, JKS | not offered | — | Gradle plugin: docuconfExport | 134 of 134 | JDK HttpServer |
| Rust | figment + serde | docuconf (crates.io) | all 9 | json | go | json, yaml, toml | PKCS#12 | rejected at declaration | figment profiles | docuconf::export() | 134 of 134 | std::net |
| Swift | swift-configuration | Docuconf (SwiftPM) | all 9 | csv | seconds | json, yaml | PKCS#12, JKS (integrity check) | when the app consumes changes | — | app docuconf-export | 132 of 134 (skips json-schema) | POSIX sockets |
| Elixir | config/runtime.exs | docuconf (Hex) | all 9 | csv | go | json (yaml, toml via a decoder) | PKCS#12, JKS (integrity check) | with Docuconf.Watcher | — | mix docuconf.export | 134 of 134 | :httpd |
| Gleam | envoy + gleam/dynamic/decode | docuconf_gleam (Hex) | all 9 | csv | go | json (others via a parser) | format check only | not offered | — | docuconf.write_contract() | 132 of 134 on Erlang, 131 on JavaScript (skips json-schema; int64 on JavaScript) | wisp |
| C++ | CLI11 | docuconf (CMake FetchContent) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | rejected at declaration | — | app --docuconf-export | 134 of 134 | cpp-httplib |
| PHP (Laravel) | Laravel config + vlucas/phpdotenv | docuconf/docuconf (Packagist) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | rejected at declaration | — | php artisan docuconf:export | 134 of 134 | Laravel |
| PHP (Symfony) | Symfony config + %env()% processors | docuconf/docuconf (Packagist) | all 9 | csv | go | json, yaml, toml | PKCS#12, JKS (integrity check) | rejected at declaration | — | bin/console docuconf:export | 134 of 134 | Symfony |
| COBOL | GnuCOBOL copybooks + docuconf exec | docuconf-cobol (Go module) | all 9 | csv | go | json, yaml (checked by docuconf exec) | PKCS#12 (checked by docuconf exec) | not offered | — | docuconf-cobol generate | 134 of 134 (loader under docuconf exec) | batch job |
Known gaps
- Go: caarlos0/env parses int as 32-bit, so that range is exported. No TOML config files, no JKS keystores.
- TypeScript (T3 Env): ESM (.mjs) and CommonJS both supported. int is limited to the safe-integer range. Tested with Zod 4; other Standard Schema validators (Valibot, ArkType) are untested.
- TypeScript (NestJS): ESM and CommonJS builds; NestJS 11 (CommonJS) and 12 (ESM). int is limited to the safe-integer range.
- .NET: Platform-mounted appsettings overlays load through AddDocuconfOverlays<T>(). Export runs at runtime; a build-time source generator is planned.
- Python: No JKS keystores. No profiles.
- Ruby: Rails credentials must be excluded explicitly. TOML needs the tomlrb gem.
- Java (Spring Boot): Several active profiles at once (a,b) are not supported yet. The dev.docuconf Maven namespace is not verified on Maven Central yet.
- Kotlin: JVM target only so far; the core module is Kotlin Multiplatform-ready. No reload: watch.
- Rust: No reload: watch. No JKS keystores.
- Swift: Built and tested on Linux; macOS and iOS builds are untested. No TOML, no profiles.
- Elixir: Watched files reload only while the app supervises Docuconf.Watcher.
- Gleam: Keystores are not opened with their password yet. On the JavaScript target, integers beyond 2^53 lose precision. The Hex package is `docuconf_gleam`: `docuconf` is the Elixir SDK's.
- C++: No reload: watch. No profiles or config-file overlays. JKS keystores are checked by their integrity digest; their entries are not parsed.
- PHP (Laravel): No profiles or config-file overlays. No reload: watch. Run php artisan config:cache when the container starts, not in the image build.
- PHP (Symfony): No profiles or config-file overlays. No reload: watch. No Flex recipe yet: register the bundle by hand.
- COBOL: Patterns, URL schemes, JSON Schemas and file contents are checked by docuconf exec, not by the loader. A PIC X field holds bytes and contract lengths count characters: leave room for multi-byte UTF-8, or restrict values to ASCII. A value loses its trailing spaces.
Conformance suite
The shared suite lives in conformance/ in docuconf-go. Cases are written once, in YAML, and expanded into cases.json, which every SDK runs in CI through its contract-first mode:
- Values cases start from typed platform values. They are rendered with the meta-schema's
#Renderonce per list and duration encoding, so one case checks that every SDK readsa,b,["a","b"]andNAME__0/NAME__1the same way, and1m30s,PT90S,90and00:01:30as the same duration. - Env cases give the raw environment the app might see anyway (malformed values, out-of-range numbers, unresolved
vault:references) with the exact error codes every SDK must report. - A runner checks the typed result of every variable, or the exact set of variable and code pairs, and that no error output contains a secret's value.
Two SDKs that disagree on a case cannot both be right, so a failing case is a bug in an SDK or in the spec. The suite covers variables today; file inputs, profiles and overlays are tested inside each SDK until their cases are added. Each SDK's export is also checked with cue vet against the meta-schema in its own CI.