@@ -14,7 +14,7 @@ A Zig library for parsing environment variables directly into typed structs, pro
1414
1515## Why
1616
17- 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.
17+ 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.
1818
1919> [ !NOTE]
2020> This is my first ever Zig project so feel free to contribute and send PRs!
@@ -27,6 +27,7 @@ Managing configuration with environment variables is common, but environment var
2727- ✅ ** Flexible mapping** : Fields map to their names by default, optional custom mapping
2828- ✅ ** Skip fields** : Map fields to "-" to explicitly skip environment variable lookup
2929- ✅ ** Flexible boolean parsing** : Parse "true", "1", "yes" (case-insensitive) as true
30+ - ✅ ** Custom parsers** : Validation and complex parsing functions for advanced use cases
3031- ✅ ** Custom environment maps** : Load from custom maps for testing
3132
3233## Installation
@@ -46,7 +47,7 @@ const env_struct = b.dependency("env_struct", .{
4647 .target = target,
4748 .optimize = optimize,
4849});
49-
50+
5051exe.root_module.addImport("env_struct", env_struct.module("env_struct"));
5152```
5253
@@ -76,7 +77,7 @@ pub fn main() !void {
7677 const allocator = gpa.allocator();
7778
7879 const config = try env_struct.load(Config, allocator);
79-
80+
8081 std.debug.print("App: {s}\n", .{config.APP_NAME});
8182 std.debug.print("Port: {}\n", .{config.PORT});
8283}
@@ -114,7 +115,77 @@ export APP_NAME="My App"
114115export PORT=" 8080"
115116```
116117
117- ### Advanced Usage
118+ ## Advanced Usage
119+
120+ ### Custom Parsers and Validators
121+
122+ The library provides two main approaches for custom parsing:
123+
124+ #### 1. Validators (Recommended for validation)
125+ Use the ` validator ` function to combine default parsing with custom validation:
126+
127+ ``` zig
128+ const std = @import("std");
129+ const env_struct = @import("env_struct");
130+
131+ // Simple validation function
132+ fn validatePort(port: u32) !u32 {
133+ if (port > 65535) return error.InvalidPort;
134+ return port;
135+ }
136+
137+ const Config = struct {
138+ port: u32,
139+
140+ const env = .{
141+ .port = .{
142+ .key = "PORT",
143+ .parser = env_struct.validator(u32, validatePort),
144+ },
145+ };
146+ };
147+ ```
148+
149+ #### 2. Full Custom Parsers
150+ For complex parsing logic that doesn't use default parsing:
151+
152+ ``` zig
153+ // Enum parsing function
154+ const LogLevel = enum { debug, info, warn, err };
155+
156+ fn parseLogLevel(raw: []const u8, allocator: std.mem.Allocator) !LogLevel {
157+ _ = allocator; // unused in this case
158+ if (std.mem.eql(u8, raw, "debug")) return .debug;
159+ if (std.mem.eql(u8, raw, "info")) return .info;
160+ if (std.mem.eql(u8, raw, "warn")) return .warn;
161+ if (std.mem.eql(u8, raw, "error")) return .err;
162+ return error.InvalidLogLevel;
163+ }
164+
165+ const Config = struct {
166+ port: u32,
167+ log_level: LogLevel,
168+
169+ const env = .{
170+ .port = .{
171+ .key = "PORT",
172+ .parser = env_struct.validator(u32, validatePort),
173+ },
174+ .log_level = .{
175+ .key = "LOG_LEVEL",
176+ .parser = parseLogLevel,
177+ },
178+ };
179+ };
180+ ```
181+
182+ ** Key Points:**
183+ - Use ` validator() ` when you want default parsing + validation
184+ - Use custom parsers for complex parsing that doesn't follow default rules
185+ - All custom parsers use the signature: ` fn(raw: []const u8, allocator: Allocator) !T `
186+ - The ` parseValue() ` function is available for implementing custom parsers that want to reuse default parsing
187+
188+ ### Nested Structs & Complex Configuration
118189
119190``` zig
120191const Config = struct {
@@ -131,7 +202,7 @@ const Config = struct {
131202};
132203```
133204
134- ### Nested Structs & Custom Environment Maps
205+ ### Custom Environment Maps
135206
136207``` zig
137208const DatabaseConfig = struct {
@@ -170,17 +241,17 @@ const test_config = try env_struct.loadMap(ServerConfig, custom_env, allocator);
170241Fields are mapped to environment variables with these behaviors:
171242
172243- ** Default mapping** : Fields automatically map to environment variables with the same name
173- - ** Custom mapping** : Use the ` env ` declaration to map fields to different environment variable names
244+ - ** Custom mapping** : Use the ` env ` declaration to map fields to different environment variable names
174245- ** Skip mapping** : Map a field to ` "-" ` to skip environment variable lookup (must have default values or be optional)
175246- ** Field requirements** : Fields without default values must either have corresponding environment variables or be optional
176247- ** Optional env declaration** : The ` env ` declaration is only needed for custom mappings or skipping fields
177248
178249``` zig
179250const Config = struct {
180251 app_name: []const u8, // Maps to "app_name" env var
181- custom_port: u32, // Maps to "PORT" env var
252+ custom_port: u32, // Maps to "PORT" env var
182253 skipped_field: []const u8 = "default", // No env var lookup
183-
254+
184255 const env = .{
185256 .custom_port = "PORT", // Custom mapping
186257 .skipped_field = "-", // Skip mapping
@@ -189,7 +260,7 @@ const Config = struct {
189260};
190261```
191262
192- ## Supported Types
263+ ## Built-in Supported Types
193264
194265| Type | Examples | Notes |
195266| ------| ----------| -------|
@@ -198,6 +269,7 @@ const Config = struct {
198269| ` u8 ` , ` u16 ` , ` u32 ` , ` u64 ` , ` u128 ` , ` usize ` | ` "42" ` , ` "255" ` | Unsigned integers |
199270| ` f16 ` , ` f32 ` , ` f64 ` , ` f80 ` , ` f128 ` | ` "3.14" ` | Floating point |
200271| ` bool ` | ` "true" ` , ` "1" ` , ` "yes" ` | Case-insensitive |
272+ | ` enum ` | ` "debug" ` , ` "info" ` | Matches enum field names |
201273| ` ?T ` | Any valid ` T ` or missing | Optional types |
202274| ` struct ` | N/A | Nested structs |
203275
@@ -209,6 +281,39 @@ Load configuration from system environment variables.
209281### ` loadMap(comptime T: type, env_map: std.process.EnvMap, allocator: std.mem.Allocator) !T `
210282Load configuration from a custom environment map.
211283
284+ ### ` parseValue(comptime T: type, raw_value: []const u8, allocator: std.mem.Allocator) !T `
285+ Parse a raw string value into the specified type. Useful for implementing custom parsers that want to preserve default parsing behavior.
286+
287+ ### ` validator(comptime T: type, comptime validateFn: anytype) fn([]const u8, std.mem.Allocator) anyerror!T `
288+ Create a validator function that combines default parsing with custom validation. The validation function should have the signature ` fn(T) !T ` .
289+
290+ ### Custom Parser Function Signature
291+
292+ Custom parsers must follow this signature:
293+
294+ ``` zig
295+ fn parserFunction(raw_value: []const u8, allocator: std.mem.Allocator) !T
296+ ```
297+
298+ Where:
299+ - ` raw_value ` : The raw string from the environment variable
300+ - ` allocator ` : Memory allocator for dynamic allocations (can be ignored if not needed)
301+ - ` T ` : The target type to parse into
302+ - Returns the parsed value or an error
303+
304+ ### Validator Function Signature
305+
306+ Validator functions used with ` validator() ` should have this signature:
307+
308+ ``` zig
309+ fn validatorFunction(value: T) !T
310+ ```
311+
312+ Where:
313+ - ` value ` : The already-parsed value from default parsing
314+ - ` T ` : The type being validated
315+ - Returns the validated value or an error
316+
212317## Building
213318
214319``` bash
@@ -219,4 +324,4 @@ zig build
219324
220325``` bash
221326zig test src/env_struct.zig
222- ```
327+ ```
0 commit comments