derive_from_env 1.0.0

Extract type safe structured data from environment variables with procedural derive macros
Documentation
# derive_from_env

Extract type-safe structured configuration from environment variables using procedural derive macros.

All types are parsed using the [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) trait, making it easy to use with any type that implements it - including your own custom types.

## Installation

```toml
[dependencies]
derive_from_env = "0.1"
```

## Quick Start

```rust
use derive_from_env::FromEnv;

#[derive(FromEnv)]
struct Config {
    host: String,
    port: u16,
    debug: bool,
}

fn main() {
    // Reads from HOST, PORT, DEBUG environment variables
    let config = Config::from_env().unwrap();
}
```

## Field Attributes

### `default = "value"`

Provides a fallback value when the environment variable is not set.

```rust
#[derive(FromEnv)]
struct Config {
    #[from_env(default = "localhost")]
    host: String,
    #[from_env(default = "8080")]
    port: u16,
}
```

### `var = "NAME"`

Overrides the environment variable name completely. This ignores any prefix.

```rust
#[derive(FromEnv)]
#[from_env(prefix = "APP_")]
struct Config {
    #[from_env(var = "DATABASE_URL")]  // Uses DATABASE_URL, not APP_DATABASE_URL
    db_url: String,
}
```

### `rename = "name"`

Renames the field for environment variable lookup, but still respects the prefix.

```rust
#[derive(FromEnv)]
#[from_env(prefix = "APP_")]
struct Config {
    #[from_env(rename = "db_url")]  // Uses APP_DB_URL instead of APP_DATABASE_URL
    database_url: String,
}
```

### `flatten`

Marks a field as a nested struct. Required for any field that is itself a struct deriving `FromEnv`.

```rust
#[derive(FromEnv)]
struct DatabaseConfig {
    host: String,
    port: u16,
}

#[derive(FromEnv)]
struct Config {
    #[from_env(flatten)]
    database: DatabaseConfig,  // Reads from DATABASE_HOST, DATABASE_PORT
}
```

### `no_prefix`

Used with `flatten` to prevent adding the field name to the prefix chain.

```rust
#[derive(FromEnv)]
#[from_env(prefix = "APP_")]
struct Config {
    #[from_env(flatten, no_prefix)]
    database: DatabaseConfig,  // Reads from APP_HOST, APP_PORT (not APP_DATABASE_HOST)
}
```

## Struct Attributes

### `prefix = "PREFIX_"`

Sets a prefix for all environment variables in the struct.

```rust
#[derive(FromEnv)]
#[from_env(prefix = "MYAPP_")]
struct Config {
    host: String,  // Reads from MYAPP_HOST
    port: u16,     // Reads from MYAPP_PORT
}
```

Prefixes combine when nesting structs:

```rust
#[derive(FromEnv)]
#[from_env(prefix = "DB_")]
struct DatabaseConfig {
    host: String,
    port: u16,
}

#[derive(FromEnv)]
#[from_env(prefix = "APP_")]
struct Config {
    #[from_env(flatten)]
    database: DatabaseConfig,  // Reads from APP_DATABASE_DB_HOST, APP_DATABASE_DB_PORT
}
```

## Type Handling

### FromStr Types

All types are parsed using the `FromStr` trait. This includes:

- Primitives: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `f32`, `f64`, `bool`, `char`
- Standard library types: `String`, `IpAddr`, `Ipv4Addr`, `Ipv6Addr`, `SocketAddr`, `PathBuf`
- Any custom type implementing `FromStr`

```rust
use std::str::FromStr;

#[derive(Debug, PartialEq)]
enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
}

impl FromStr for LogLevel {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "debug" => Ok(LogLevel::Debug),
            "info" => Ok(LogLevel::Info),
            "warn" => Ok(LogLevel::Warn),
            "error" => Ok(LogLevel::Error),
            _ => Err(format!("unknown log level: {}", s)),
        }
    }
}

#[derive(FromEnv)]
struct Config {
    log_level: LogLevel,  // No special attribute needed!
}
```

### Option Types

`Option<T>` fields return `None` when the environment variable is not set.

```rust
#[derive(FromEnv)]
struct Config {
    host: String,           // Required
    port: Option<u16>,      // Optional, returns None if not set
}
```

## Error Handling

The `from_env()` method returns `Result<Self, FromEnvError>`.

```rust
use derive_from_env::{FromEnv, FromEnvError};

#[derive(FromEnv)]
struct Config {
    host: String,
    port: u16,
}

fn main() {
    match Config::from_env() {
        Ok(config) => println!("Loaded config: {:?}", config),
        Err(FromEnvError::MissingEnvVar { var_name }) => {
            eprintln!("Missing required variable: {}", var_name);
        }
        Err(FromEnvError::ParsingFailure { var_name, expected_type }) => {
            eprintln!("Failed to parse {} as {}", var_name, expected_type);
        }
    }
}
```

## Complete Example

```rust
use std::net::IpAddr;
use std::str::FromStr;
use derive_from_env::FromEnv;

#[derive(Debug, PartialEq)]
enum AuthMethod {
    Bearer,
    ApiKey,
}

impl FromStr for AuthMethod {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Bearer" => Ok(AuthMethod::Bearer),
            "ApiKey" => Ok(AuthMethod::ApiKey),
            _ => Err("Invalid auth method".into()),
        }
    }
}

#[derive(FromEnv)]
struct AuthConfig {
    auth_method: AuthMethod,
    api_key: String,
}

#[derive(FromEnv)]
struct DatabaseConfig {
    host: String,
    #[from_env(default = "5432")]
    port: u16,
}

#[derive(FromEnv)]
#[from_env(prefix = "APP_")]
struct AppConfig {
    #[from_env(default = "0.0.0.0")]
    bind_addr: IpAddr,

    #[from_env(default = "8080")]
    port: u16,

    debug: Option<bool>,

    #[from_env(flatten)]
    database: DatabaseConfig,

    #[from_env(flatten, no_prefix)]
    auth: AuthConfig,
}

fn main() {
    // Set environment variables
    std::env::set_var("APP_DATABASE_HOST", "localhost");
    std::env::set_var("AUTH_METHOD", "Bearer");
    std::env::set_var("API_KEY", "secret");

    let config = AppConfig::from_env().unwrap();

    assert_eq!(config.bind_addr, "0.0.0.0".parse().unwrap());
    assert_eq!(config.port, 8080);
    assert_eq!(config.debug, None);
    assert_eq!(config.database.host, "localhost");
    assert_eq!(config.database.port, 5432);
    assert_eq!(config.auth.auth_method, AuthMethod::Bearer);
    assert_eq!(config.auth.api_key, "secret");
}
```

## License

MIT