Skip to content

xarunoba/env-struct

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

23 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@xarunoba/env-struct 🌱

Static Badge Static Badge GitHub License

env-struct β€” environment variables to typed structs

A Zig library for parsing environment variables directly into typed structs, providing automatic type conversion and validation.

Note

This library does not read environment variables from files; it only parses existing environment variables into a struct.

Warning

env-struct is currently in v0. Every release might have breaking changes before v1.0.0. Make sure to specify the version you'd like to use.

Why

Managing configuration with environment variables is common, but environment variables are always strings and require manual parsing and validation. env-struct eliminates boilerplate by mapping environment variables directly to typed Zig structs, providing automatic type conversion and validation at load time. This approach improves safety, reduces errors, and makes configuration handling more robust and maintainable.

Note

This is my first ever Zig project so feel free to contribute and send PRs!

Features

  • βœ… Type-safe: Automatically parse environment variables into the correct types
  • βœ… Multiple types: Strings, integers, floats, booleans, and nested structs
  • βœ… Optional fields: Support for optional fields with defaults
  • βœ… Flexible mapping: Fields map to their names by default, optional custom mapping
  • βœ… Skip fields: Map fields to "-" to explicitly skip environment variable lookup
  • βœ… Flexible boolean parsing: Parse "true", "1", "yes" (case-insensitive) as true
  • βœ… Custom environment maps: Load from custom maps for testing

Installation

Using zig fetch (Recommended)

Add this library to your project using zig fetch:

zig fetch --save "git+https://github.com/xarunoba/env-struct#main"

Then in your build.zig:

const env_struct = b.dependency("env_struct", .{
    .target = target,
    .optimize = optimize,
});
    
exe.root_module.addImport("env_struct", env_struct.module("env_struct"));

Direct Copy

Alternatively, you can directly copy the env_struct.zig file from the src/ directory into your project and import it locally to prevent any external dependencies:

const env_struct = @import("env_struct.zig");

Usage

const std = @import("std");
const env_struct = @import("env_struct");

const Config = struct {
    APP_NAME: []const u8,    // Maps to "APP_NAME" env var
    PORT: u32,               // Maps to "PORT" env var
    DEBUG: bool = false,     // Maps to "DEBUG" env var, defaults to false
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const config = try env_struct.load(Config, allocator);
    
    std.debug.print("App: {s}\n", .{config.APP_NAME});
    std.debug.print("Port: {}\n", .{config.PORT});
}

Set environment variables:

export APP_NAME="My App"
export PORT="8080"

Custom Mapping

const Config = struct {
    name: []const u8,
    port: u32,
    debug: bool = false,
    timeout: ?f32 = null,

    const env = .{
        .name = "APP_NAME",
        .port = "PORT",
        .debug = "DEBUG",
        .timeout = "TIMEOUT",
    };
};

const config = try env_struct.load(Config, allocator);

Set environment variables:

export APP_NAME="My App"
export PORT="8080"

Advanced Usage

const Config = struct {
    app_name: []const u8,           // Maps to "app_name" env var
    custom_port: u32,               // Maps to "PORT" env var (custom mapping)
    debug: bool = false,            // Maps to "debug" env var, uses default
    internal_field: []const u8 = "computed",  // Skipped from env lookup
    optional_feature: ?u32,         // Maps to "optional_feature", can be null

    const env = .{
        .custom_port = "PORT",      // Custom environment variable name
        .internal_field = "-",      // Skip environment variable lookup
    };
};

Nested Structs & Custom Environment Maps

const DatabaseConfig = struct {
    host: []const u8,
    port: u32 = 5432,

    const env = .{
        .host = "DB_HOST",
        .port = "DB_PORT",
    };
};

const ServerConfig = struct {
    host: []const u8 = "localhost",
    port: u32,
    database: DatabaseConfig,

    const env = .{
        .host = "SERVER_HOST",
        .port = "SERVER_PORT",
    };
};

// Load from system environment
const config = try env_struct.load(ServerConfig, allocator);

// Or load from custom environment map (useful for testing)
var custom_env = std.process.EnvMap.init(allocator);
defer custom_env.deinit();
try custom_env.put("SERVER_PORT", "3000");
const test_config = try env_struct.loadMap(ServerConfig, custom_env, allocator);

Mapping Rules

Fields are mapped to environment variables with these behaviors:

  • Default mapping: Fields automatically map to environment variables with the same name
  • Custom mapping: Use the env declaration to map fields to different environment variable names
  • Skip mapping: Map a field to "-" to skip environment variable lookup (must have default values or be optional)
  • Field requirements: Fields without default values must either have corresponding environment variables or be optional
  • Optional env declaration: The env declaration is only needed for custom mappings or skipping fields
const Config = struct {
    app_name: []const u8,           // Maps to "app_name" env var
    custom_port: u32,               // Maps to "PORT" env var  
    skipped_field: []const u8 = "default",  // No env var lookup
    
    const env = .{
        .custom_port = "PORT",      // Custom mapping
        .skipped_field = "-",       // Skip mapping
        // app_name uses default mapping
    };
};

Supported Types

Type Examples Notes
[]const u8 "hello" String values
i8, i16, i32, i64, i128, isize "42", "-123" Signed integers
u8, u16, u32, u64, u128, usize "42", "255" Unsigned integers
f16, f32, f64, f80, f128 "3.14" Floating point
bool "true", "1", "yes" Case-insensitive
?T Any valid T or missing Optional types
struct N/A Nested structs

API

load(comptime T: type, allocator: std.mem.Allocator) !T

Load configuration from system environment variables.

loadMap(comptime T: type, env_map: std.process.EnvMap, allocator: std.mem.Allocator) !T

Load configuration from a custom environment map.

Building

zig build

Testing

zig test src/env_struct.zig

About

🌱 β€” Parse environment variables into typed structs in Zig

Topics

Resources

License

Stars

Watchers

Forks

Languages