env_cast/
lib.rs

1//! # env-cast
2//!
3//! [`env_cast!()`](env_cast!) reads an environment variable just like [`env!()`](env!), but parses it into a specific type.
4//!
5//! Supported types are currently
6//! `i8, u8, i16, u16, i32, u32, i64, u64, f32, f64`.
7//!
8//! ## Example
9//! ```rust
10//! use env_cast::env_cast;
11//! let PKG_MAJOR: u32 = env_cast!("CARGO_PKG_VERSION_MAJOR" as u32);
12//! ```
13//!
14
15use std::str::FromStr;
16
17use proc_macro2::Span;
18use quote::{quote, quote_spanned, ToTokens};
19use syn::{parse_macro_input, spanned::Spanned};
20
21/// Reads an environment variable just like [`env!("XXX")`](env!), but parses it into a specific type.
22///
23/// # Example
24/// ```
25/// use env_cast::env_cast;
26/// let PKG_MAJOR: u32 = env_cast!("CARGO_PKG_VERSION_MAJOR" as u32);
27/// ```
28///
29#[proc_macro]
30pub fn env_cast(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
31    let cast = parse_macro_input!(args as syn::Expr);
32
33    let (var, ty) = if let syn::Expr::Cast(cast) = cast {
34        (cast.expr, cast.ty)
35    } else {
36        return compile_error("Must be of the form <var> as <type>", cast.span());
37    };
38
39    let lit = if let syn::Expr::Lit(lit) = &*var {
40        lit
41    } else {
42        return compile_error("Must be a string literal", var.span());
43    };
44
45    let lit_str = if let syn::Lit::Str(lit_str) = &lit.lit {
46        lit_str.value()
47    } else {
48        return compile_error("Must be a string literal", lit.span());
49    };
50
51    let cast_as = match &*ty {
52        syn::Type::Path(path) => {
53            if path.path.is_ident("i8") {
54                CastAs::I8
55            } else if path.path.is_ident("u8") {
56                CastAs::U8
57            } else if path.path.is_ident("i16") {
58                CastAs::I16
59            } else if path.path.is_ident("u16") {
60                CastAs::U16
61            } else if path.path.is_ident("i32") {
62                CastAs::I32
63            } else if path.path.is_ident("u32") {
64                CastAs::U32
65            } else if path.path.is_ident("i64") {
66                CastAs::I64
67            } else if path.path.is_ident("u64") {
68                CastAs::U64
69            } else if path.path.is_ident("f32") {
70                CastAs::F32
71            } else if path.path.is_ident("f64") {
72                CastAs::F64
73            } else {
74                return compile_error(
75                    "Must be one of i8, u8, i16, u16, i32, u32, i64, u64, f32, f64",
76                    ty.span(),
77                );
78            }
79        }
80        _ => {
81            return compile_error(
82                "Must be one of i8, u8, i16, u16, i32, u32, i64, u64, f32, f64",
83                ty.span(),
84            );
85        }
86    };
87
88    let val_str = match std::env::var(lit_str) {
89        Ok(val) => val,
90        Err(err) => {
91            return compile_error(&format!("Failed to read environment: {}", err), lit.span())
92        }
93    };
94
95    match cast_as {
96        CastAs::I8 => parse::<i8>(&val_str, lit.span()),
97        CastAs::U8 => parse::<u8>(&val_str, lit.span()),
98        CastAs::I16 => parse::<i16>(&val_str, lit.span()),
99        CastAs::U16 => parse::<u16>(&val_str, lit.span()),
100        CastAs::I32 => parse::<i32>(&val_str, lit.span()),
101        CastAs::U32 => parse::<u32>(&val_str, lit.span()),
102        CastAs::I64 => parse::<i64>(&val_str, lit.span()),
103        CastAs::U64 => parse::<u64>(&val_str, lit.span()),
104        CastAs::F32 => parse::<f32>(&val_str, lit.span()),
105        CastAs::F64 => parse::<f64>(&val_str, lit.span()),
106    }
107}
108
109fn compile_error(msg: &str, span: Span) -> proc_macro::TokenStream {
110    quote_spanned! {span=>
111        compile_error!(#msg);
112    }
113    .into()
114}
115
116enum CastAs {
117    I8,
118    U8,
119    I16,
120    U16,
121    I32,
122    U32,
123    I64,
124    U64,
125    F32,
126    F64,
127}
128
129fn parse<T: FromStr + ToTokens>(str: &str, span: Span) -> proc_macro::TokenStream {
130    match str.parse::<T>() {
131        Ok(val) => quote! {#val}.into(),
132        Err(_) => compile_error("Environment variable not parseable", span),
133    }
134}