tryphon/
decoders.rs

1//! Built-in implementations of [`ConfigValueDecoder`] for common types.
2//!
3//! This module provides decoder implementations for:
4//! - `String` - passes through the raw value unchanged
5//! - **Primitive types**: `bool`, `char`
6//! - **Integers**: `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`
7//! - **Floats**: `f32`, `f64`
8//! - **Non-zero integers**: `NonZeroU8`, `NonZeroU16`, `NonZeroU32`, `NonZeroU64`, `NonZeroU128`, `NonZeroUsize`, `NonZeroI8`, `NonZeroI16`, `NonZeroI32`, `NonZeroI64`, `NonZeroI128`, `NonZeroIsize`
9//! - **Network types**: `IpAddr`, `Ipv4Addr`, `Ipv6Addr`, `SocketAddr`, `SocketAddrV4`, `SocketAddrV6`
10//! - **Path types**: `PathBuf`
11//! - **Wrappers**: `Option<T>`, `Secret<T>`
12//!
13//! # Examples
14//!
15//! ## Basic Types
16//!
17//! ```rust
18//! use tryphon::{Config, Secret};
19//!
20//! #[derive(Config)]
21//! struct AppConfig {
22//!     // String decoder - passes through raw value
23//!     #[env("APP_NAME")]
24//!     name: String,
25//!
26//!     // Numeric decoder - parses from string
27//!     #[env("PORT")]
28//!     port: u16,
29//!
30//!     // Boolean decoder - parses true/false
31//!     #[env("DEBUG")]
32//!     debug: bool,
33//!
34//!     // Character decoder
35//!     #[env("SEPARATOR")]
36//!     #[default(',')]
37//!     separator: char,
38//!
39//!     // Option decoder - makes field optional
40//!     #[env("LOG_LEVEL")]
41//!     log_level: Option<String>,
42//!
43//!     // Secret decoder - masks value in output
44//!     #[env("API_KEY")]
45//!     api_key: Secret<String>,
46//! }
47//! ```
48//!
49//! ## Network and Path Types
50//!
51//! ```rust
52//! use tryphon::Config;
53//! use std::net::{IpAddr, SocketAddr};
54//! use std::path::PathBuf;
55//!
56//! #[derive(Config)]
57//! struct ServerConfig {
58//!     // IP address decoder - parses from "192.168.1.1" or "::1"
59//!     #[env("BIND_IP")]
60//!     bind_ip: IpAddr,
61//!
62//!     // Socket address decoder - parses from "127.0.0.1:8080"
63//!     #[env("LISTEN_ADDR")]
64//!     listen_addr: SocketAddr,
65//!
66//!     // Path decoder - parses from "/var/lib/myapp"
67//!     #[env("DATA_DIR")]
68//!     data_dir: PathBuf,
69//! }
70//! ```
71//!
72//! ## Non-Zero Types
73//!
74//! ```rust
75//! use tryphon::Config;
76//! use std::num::NonZeroU32;
77//!
78//! #[derive(Config)]
79//! struct PoolConfig {
80//!     // Non-zero ensures value cannot be 0
81//!     // Will fail to parse if value is "0"
82//!     #[env("POOL_SIZE")]
83//!     pool_size: NonZeroU32,
84//!
85//!     #[env("MAX_CONNECTIONS")]
86//!     max_connections: NonZeroU32,
87//! }
88//! ```
89
90use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
91use std::num::{
92    NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
93    NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
94};
95use std::path::PathBuf;
96
97use crate::config_value_decoder::ConfigValueDecoder;
98use crate::secret::Secret;
99
100impl ConfigValueDecoder for String {
101    fn decode(raw: String) -> Result<String, String> {
102        Ok(raw)
103    }
104}
105
106/// Internal macro to generate `ConfigValueDecoder` implementations for types
107/// that implement `FromStr`.
108///
109/// Used to implement decoders for primitive types like numbers and booleans.
110/// If parsing fails, the `FromStr` error is wrapped in a `ConfigFieldError::ParsingError`.
111macro_rules! make_config_value_decoder {
112    ($ty: tt) => {
113        impl ConfigValueDecoder for $ty {
114            fn decode(raw: String) -> Result<$ty, String> {
115                raw.parse::<$ty>().map_err(|e| e.to_string())
116            }
117        }
118    };
119}
120
121/// Internal macro to generate `ConfigValueDecoder` implementations for wrapper
122/// types that contain a decodable type.
123///
124/// Used to implement decoders for `Option<T>` and `Secret<T>`, which wrap
125/// an inner type `T` that itself implements `ConfigValueDecoder`.
126macro_rules! make_nested_config_value_decoder {
127    ($ty: tt, $constr: expr) => {
128        impl<T: ConfigValueDecoder> ConfigValueDecoder for $ty<T> {
129            fn decode(raw: String) -> Result<$ty<T>, String> {
130                T::decode(raw).map($constr)
131            }
132        }
133    };
134}
135
136// Wrapper types
137make_nested_config_value_decoder!(Secret, Secret);
138make_nested_config_value_decoder!(Option, Some);
139
140// Primitive types
141make_config_value_decoder!(bool);
142make_config_value_decoder!(char);
143
144// Unsigned integers
145make_config_value_decoder!(u8);
146make_config_value_decoder!(u16);
147make_config_value_decoder!(u32);
148make_config_value_decoder!(u64);
149make_config_value_decoder!(u128);
150make_config_value_decoder!(usize);
151
152// Signed integers
153make_config_value_decoder!(i8);
154make_config_value_decoder!(i16);
155make_config_value_decoder!(i32);
156make_config_value_decoder!(i64);
157make_config_value_decoder!(i128);
158make_config_value_decoder!(isize);
159
160// Floating point
161make_config_value_decoder!(f32);
162make_config_value_decoder!(f64);
163
164// Non-zero unsigned integers
165make_config_value_decoder!(NonZeroU8);
166make_config_value_decoder!(NonZeroU16);
167make_config_value_decoder!(NonZeroU32);
168make_config_value_decoder!(NonZeroU64);
169make_config_value_decoder!(NonZeroU128);
170make_config_value_decoder!(NonZeroUsize);
171
172// Non-zero signed integers
173make_config_value_decoder!(NonZeroI8);
174make_config_value_decoder!(NonZeroI16);
175make_config_value_decoder!(NonZeroI32);
176make_config_value_decoder!(NonZeroI64);
177make_config_value_decoder!(NonZeroI128);
178make_config_value_decoder!(NonZeroIsize);
179
180// Network types
181make_config_value_decoder!(IpAddr);
182make_config_value_decoder!(Ipv4Addr);
183make_config_value_decoder!(Ipv6Addr);
184make_config_value_decoder!(SocketAddr);
185make_config_value_decoder!(SocketAddrV4);
186make_config_value_decoder!(SocketAddrV6);
187
188// Path types
189make_config_value_decoder!(PathBuf);
190
191#[cfg(test)]
192mod tests {
193    use crate::*;
194    use std::net::*;
195    use std::num::*;
196
197    #[test]
198    fn test_bool_decoder() {
199        assert!(bool::decode("true".to_string()).unwrap());
200        assert!(!bool::decode("false".to_string()).unwrap());
201        assert!(bool::decode("invalid".to_string()).is_err());
202    }
203
204    #[test]
205    fn test_char_decoder() {
206        assert_eq!(char::decode("a".to_string()).unwrap(), 'a');
207        assert_eq!(char::decode("€".to_string()).unwrap(), '€');
208        assert!(char::decode("ab".to_string()).is_err());
209        assert!(char::decode("".to_string()).is_err());
210    }
211
212    #[test]
213    fn test_unsigned_integer_decoders() {
214        // Test each unsigned integer type with valid input
215        assert_eq!(u8::decode("255".to_string()).unwrap(), 255u8);
216        assert_eq!(u16::decode("65535".to_string()).unwrap(), 65535u16);
217        assert_eq!(
218            u32::decode("4294967295".to_string()).unwrap(),
219            4294967295u32
220        );
221        assert_eq!(
222            u64::decode("18446744073709551615".to_string()).unwrap(),
223            18446744073709551615u64
224        );
225        assert_eq!(
226            u128::decode("340282366920938463463374607431768211455".to_string()).unwrap(),
227            340282366920938463463374607431768211455u128
228        );
229        assert_eq!(usize::decode("1000".to_string()).unwrap(), 1000usize);
230
231        // Test overflow
232        assert!(u8::decode("256".to_string()).is_err());
233        assert!(u16::decode("65536".to_string()).is_err());
234
235        // Test invalid input
236        assert!(u32::decode("not_a_number".to_string()).is_err());
237        assert!(u64::decode("-1".to_string()).is_err());
238    }
239
240    #[test]
241    fn test_signed_integer_decoders() {
242        // Test each signed integer type with valid input
243        assert_eq!(i8::decode("-128".to_string()).unwrap(), -128i8);
244        assert_eq!(i16::decode("-32768".to_string()).unwrap(), -32768i16);
245        assert_eq!(
246            i32::decode("-2147483648".to_string()).unwrap(),
247            -2147483648i32
248        );
249        assert_eq!(
250            i64::decode("-9223372036854775808".to_string()).unwrap(),
251            -9223372036854775808i64
252        );
253        assert_eq!(
254            i128::decode("-170141183460469231731687303715884105728".to_string()).unwrap(),
255            -170141183460469231731687303715884105728i128
256        );
257        assert_eq!(isize::decode("-1000".to_string()).unwrap(), -1000isize);
258
259        // Test positive values
260        assert_eq!(i8::decode("127".to_string()).unwrap(), 127i8);
261        assert_eq!(i32::decode("42".to_string()).unwrap(), 42i32);
262
263        // Test overflow
264        assert!(i8::decode("128".to_string()).is_err());
265        assert!(i16::decode("32768".to_string()).is_err());
266
267        // Test invalid input
268        assert!(i32::decode("not_a_number".to_string()).is_err());
269    }
270
271    #[test]
272    #[allow(clippy::approx_constant)]
273    fn test_float_decoders() {
274        assert_eq!(f32::decode("3.14".to_string()).unwrap(), 3.14f32);
275        assert_eq!(f64::decode("2.71828".to_string()).unwrap(), 2.71828f64);
276        assert_eq!(f32::decode("-1.5".to_string()).unwrap(), -1.5f32);
277        assert_eq!(f64::decode("0.0".to_string()).unwrap(), 0.0f64);
278        assert_eq!(f32::decode("1e10".to_string()).unwrap(), 1e10f32);
279        assert_eq!(f64::decode("1.5e-5".to_string()).unwrap(), 1.5e-5f64);
280
281        // Test invalid input
282        assert!(f32::decode("not_a_float".to_string()).is_err());
283    }
284
285    #[test]
286    fn test_nonzero_unsigned_decoders() {
287        assert_eq!(NonZeroU8::decode("1".to_string()).unwrap().get(), 1);
288        assert_eq!(NonZeroU16::decode("100".to_string()).unwrap().get(), 100);
289        assert_eq!(NonZeroU32::decode("1000".to_string()).unwrap().get(), 1000);
290        assert_eq!(
291            NonZeroU64::decode("10000".to_string()).unwrap().get(),
292            10000
293        );
294        assert_eq!(
295            NonZeroU128::decode("100000".to_string()).unwrap().get(),
296            100000
297        );
298        assert_eq!(NonZeroUsize::decode("42".to_string()).unwrap().get(), 42);
299
300        assert!(NonZeroU8::decode("0".to_string()).is_err());
301        assert!(NonZeroU32::decode("0".to_string()).is_err());
302        assert_eq!(
303            NonZeroU16::decode("not_a_number".to_string()).unwrap_err(),
304            "invalid digit found in string"
305        );
306    }
307
308    #[test]
309    fn test_nonzero_signed_decoders() {
310        assert_eq!(NonZeroI8::decode("1".to_string()).unwrap().get(), 1);
311        assert_eq!(NonZeroI16::decode("-100".to_string()).unwrap().get(), -100);
312        assert_eq!(NonZeroI32::decode("1000".to_string()).unwrap().get(), 1000);
313        assert_eq!(
314            NonZeroI64::decode("-10000".to_string()).unwrap().get(),
315            -10000
316        );
317        assert_eq!(
318            NonZeroI128::decode("100000".to_string()).unwrap().get(),
319            100000
320        );
321        assert_eq!(NonZeroIsize::decode("-42".to_string()).unwrap().get(), -42);
322
323        assert!(NonZeroI8::decode("0".to_string()).is_err());
324        assert!(NonZeroI32::decode("0".to_string()).is_err());
325        assert_eq!(
326            NonZeroI16::decode("not_a_number".to_string()).unwrap_err(),
327            "invalid digit found in string"
328        );
329    }
330
331    #[test]
332    fn test_ip_address_decoders() {
333        assert_eq!(
334            IpAddr::decode("192.168.1.1".to_string()).unwrap(),
335            "192.168.1.1".parse::<IpAddr>().unwrap()
336        );
337        assert_eq!(
338            IpAddr::decode("::1".to_string()).unwrap(),
339            "::1".parse::<IpAddr>().unwrap()
340        );
341
342        assert_eq!(
343            Ipv4Addr::decode("127.0.0.1".to_string()).unwrap(),
344            Ipv4Addr::new(127, 0, 0, 1)
345        );
346        assert_eq!(
347            Ipv4Addr::decode("192.168.0.1".to_string()).unwrap(),
348            Ipv4Addr::new(192, 168, 0, 1)
349        );
350
351        // Test Ipv6Addr
352        assert_eq!(
353            Ipv6Addr::decode("::1".to_string()).unwrap(),
354            Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)
355        );
356        assert_eq!(
357            Ipv6Addr::decode("2001:db8::1".to_string()).unwrap(),
358            "2001:db8::1".parse::<Ipv6Addr>().unwrap()
359        );
360
361        // Test invalid input
362        assert_eq!(
363            Ipv4Addr::decode("256.1.1.1".to_string()).unwrap_err(),
364            "invalid IPv4 address syntax"
365        );
366        assert_eq!(
367            Ipv6Addr::decode("not_an_ip".to_string()).unwrap_err(),
368            "invalid IPv6 address syntax"
369        );
370    }
371
372    #[test]
373    fn test_socket_address_decoders() {
374        assert_eq!(
375            SocketAddr::decode("127.0.0.1:8080".to_string()).unwrap(),
376            "127.0.0.1:8080".parse::<SocketAddr>().unwrap()
377        );
378        assert_eq!(
379            SocketAddr::decode("[::1]:8080".to_string()).unwrap(),
380            "[::1]:8080".parse::<SocketAddr>().unwrap()
381        );
382
383        // Test SocketAddrV4
384        assert_eq!(
385            SocketAddrV4::decode("192.168.1.1:3000".to_string()).unwrap(),
386            "192.168.1.1:3000".parse::<SocketAddrV4>().unwrap()
387        );
388
389        // Test SocketAddrV6
390        assert_eq!(
391            SocketAddrV6::decode("[2001:db8::1]:8080".to_string()).unwrap(),
392            "[2001:db8::1]:8080".parse::<SocketAddrV6>().unwrap()
393        );
394
395        // Test invalid input
396        assert_eq!(
397            SocketAddr::decode("127.0.0.1".to_string()).unwrap_err(),
398            "invalid socket address syntax"
399        );
400        assert_eq!(
401            SocketAddr::decode("not_a_socket".to_string()).unwrap_err(),
402            "invalid socket address syntax"
403        );
404    }
405
406    #[test]
407    fn test_option_decoder() {
408        // Option wraps another decoder
409        assert_eq!(
410            Option::<String>::decode("hello".to_string()).unwrap(),
411            Some("hello".to_string())
412        );
413        assert_eq!(Option::<i32>::decode("42".to_string()).unwrap(), Some(42));
414        assert_eq!(
415            Option::<bool>::decode("true".to_string()).unwrap(),
416            Some(true)
417        );
418
419        assert!(Option::<i32>::decode("not_a_number".to_string()).is_err());
420    }
421
422    #[test]
423    fn test_secret_decoder() {
424        // Secret wraps another decoder
425        let secret = Secret::<String>::decode("my_secret".to_string()).unwrap();
426        assert_eq!(*secret, "my_secret");
427
428        let secret = Secret::<i32>::decode("42".to_string()).unwrap();
429        assert_eq!(*secret, 42);
430
431        assert!(Secret::<i32>::decode("not_a_number".to_string()).is_err());
432    }
433}