Skip to content

Commit 38e8db0

Browse files
domenkozarclaude
andcommitted
Update README to accurately reflect secretspec-derive usage
- Remove basic SDK usage section, making typed codegen the primary method - Clarify field naming convention (lowercase transformation) - Add detailed explanation of type rules and when fields become Option<String> - Improve examples showing union types vs profile-specific types - Document all generated types (SecretSpec, SecretSpecProfile, Profile, Provider) - Make examples more practical and accurate to actual implementation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent abc832c commit 38e8db0

File tree

1 file changed

+56
-58
lines changed

1 file changed

+56
-58
lines changed

README.md

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -156,82 +156,80 @@ $ secretspec check --provider env
156156
```
157157

158158

159-
## Rust SDK with Type-Safe Code Generation
159+
## Rust SDK
160+
161+
SecretSpec provides a proc macro that generates strongly-typed Rust structs from your `secretspec.toml` file at compile time.
162+
163+
### Add to your `Cargo.toml`:
164+
```toml
165+
[dependencies]
166+
secretspec = { version = "0.1", features = ["codegen"] }
167+
```
160168

161169
### Basic Usage
162170

163171
```rust
164-
use secretspec::SecretSpec;
172+
// Generate typed structs from secretspec.toml
173+
secretspec::define_secrets!("secretspec.toml");
165174

166175
fn main() -> Result<(), Box<dyn std::error::Error>> {
167-
let spec = SecretSpec::load()?;
176+
// Load secrets with type-safe struct
177+
let secrets = SecretSpec::load(Provider::Keyring)?;
178+
179+
// Field names are lowercased versions of secret names
180+
println!("Database: {}", secrets.database_url); // DATABASE_URL -> database_url
168181

169-
// Validate and get all secrets for the current environment
170-
let secrets = spec.validate(None, None)?;
182+
// Optional secrets are Option<String>
183+
if let Some(redis) = &secrets.redis_url {
184+
println!("Redis: {}", redis);
185+
}
171186

172-
// Access individual secrets
173-
let db_url = secrets.get("DATABASE_URL")
174-
.ok_or("DATABASE_URL not found")?;
187+
// Set all secrets as environment variables
188+
secrets.set_as_env_vars();
175189

176-
println!("Connecting to: {}", db_url);
177190
Ok(())
178191
}
179192
```
180193

181-
### Type-Safe Code Generation
194+
### Profile-Specific Types
182195

183-
SecretSpec uses a proc macro to generate strongly-typed Rust structs from your `secretspec.toml` file:
196+
The macro generates exact types for each profile, ensuring compile-time safety:
184197

185-
1. **Add to your `Cargo.toml`:**
186-
```toml
187-
[dependencies]
188-
secretspec = { version = "0.1", features = ["codegen"] }
189-
```
198+
```rust
199+
// Load with profile-specific types for maximum type safety
200+
match SecretSpec::load_profile(Provider::Keyring, Profile::Production)? {
201+
SecretSpecProfile::Production { api_key, database_url, redis_url, .. } => {
202+
// In production: api_key is String (required)
203+
// database_url is String (required)
204+
// redis_url might be String or Option<String> based on config
205+
println!("Production API key: {}", api_key);
206+
}
207+
SecretSpecProfile::Development { api_key, database_url, .. } => {
208+
// In development: api_key is Option<String> (has default)
209+
// database_url is Option<String> (has default)
210+
if let Some(key) = api_key {
211+
println!("Dev API key: {}", key);
212+
}
213+
}
214+
_ => {}
215+
}
216+
```
190217

191-
2. **Use the proc macro in your code:**
192-
```rust
193-
// Generate typed structs from secretspec.toml
194-
secretspec::define_secrets!("secretspec.toml");
195-
196-
use secretspec::codegen::Provider;
197-
198-
fn main() -> Result<(), Box<dyn std::error::Error>> {
199-
// Load with strongly-typed struct
200-
let secrets = SecretSpec::load(Provider::Keyring)?;
201-
202-
// Required secrets are guaranteed to exist
203-
println!("Database: {}", secrets.database_url);
204-
205-
// Optional secrets are Option<String>
206-
if let Some(redis) = &secrets.redis_url {
207-
println!("Redis: {}", redis);
208-
}
209-
210-
// Set all secrets as environment variables
211-
secrets.set_as_env_vars();
212-
213-
Ok(())
214-
}
215-
```
218+
### Generated Types
216219

217-
3. **Load with specific profile for exact types:**
218-
```rust
219-
// Load with profile-specific types
220-
match SecretSpec::load_profile(Provider::Keyring, Profile::Production) {
221-
Ok(SecretSpecProfile::Production { api_key, database_url, .. }) => {
222-
// In production, api_key is String (required)
223-
println!("API Key: {}", api_key);
224-
}
225-
_ => unreachable!(),
226-
}
227-
```
220+
The macro generates several types based on your `secretspec.toml`:
221+
222+
- **`SecretSpec`** - Main struct with union types (fields are `Option<String>` if optional in *any* profile)
223+
- **`SecretSpecProfile`** - Enum with profile-specific variants containing exact types
224+
- **`Profile`** - Enum of all profiles from your config (e.g., `Development`, `Production`)
225+
- **`Provider`** - Type-safe provider selection (`Keyring`, `Dotenv`, `Env`)
226+
227+
### Type Rules
228228

229-
The macro generates:
230-
- `SecretSpec` struct with union types (safe for any profile)
231-
- `SecretSpecProfile` enum with exact types for each profile
232-
- `Profile` enum with all profiles from your TOML
233-
- Required secrets as `String` or `Option<String>` based on profile requirements
234-
- Compile-time type safety
229+
- Secret fields are named as lowercase versions of the environment variable (e.g., `DATABASE_URL``database_url`)
230+
- A field is `String` if it's required and has no default in ALL profiles
231+
- A field is `Option<String>` if it's optional or has a default in ANY profile
232+
- Profile-specific types reflect the exact requirements for that profile
235233

236234
## Adding a New Provider Backend
237235

0 commit comments

Comments
 (0)