env_cast/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! # env-cast
//!
//! [`env_cast!()`](env_cast!) reads an environment variable just like [`env!()`](env!), but parses it into a specific type.
//!
//! Supported types are currently
//! `i8, u8, i16, u16, i32, u32, i64, u64, f32, f64`.
//!
//! ## Example
//! ```rust
//! use env_cast::env_cast;
//! let PKG_MAJOR: u32 = env_cast!("CARGO_PKG_VERSION_MAJOR" as u32);
//! ```
//!

use std::str::FromStr;

use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens};
use syn::{parse_macro_input, spanned::Spanned};

/// Reads an environment variable just like [`env!("XXX")`](env!), but parses it into a specific type.
///
/// # Example
/// ```
/// use env_cast::env_cast;
/// let PKG_MAJOR: u32 = env_cast!("CARGO_PKG_VERSION_MAJOR" as u32);
/// ```
///
#[proc_macro]
pub fn env_cast(args: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let cast = parse_macro_input!(args as syn::Expr);

    let (var, ty) = if let syn::Expr::Cast(cast) = cast {
        (cast.expr, cast.ty)
    } else {
        return compile_error("Must be of the form <var> as <type>", cast.span());
    };

    let lit = if let syn::Expr::Lit(lit) = &*var {
        lit
    } else {
        return compile_error("Must be a string literal", var.span());
    };

    let lit_str = if let syn::Lit::Str(lit_str) = &lit.lit {
        lit_str.value()
    } else {
        return compile_error("Must be a string literal", lit.span());
    };

    let cast_as = match &*ty {
        syn::Type::Path(path) => {
            if path.path.is_ident("i8") {
                CastAs::I8
            } else if path.path.is_ident("u8") {
                CastAs::U8
            } else if path.path.is_ident("i16") {
                CastAs::I16
            } else if path.path.is_ident("u16") {
                CastAs::U16
            } else if path.path.is_ident("i32") {
                CastAs::I32
            } else if path.path.is_ident("u32") {
                CastAs::U32
            } else if path.path.is_ident("i64") {
                CastAs::I64
            } else if path.path.is_ident("u64") {
                CastAs::U64
            } else if path.path.is_ident("f32") {
                CastAs::F32
            } else if path.path.is_ident("f64") {
                CastAs::F64
            } else {
                return compile_error(
                    "Must be one of i8, u8, i16, u16, i32, u32, i64, u64, f32, f64",
                    ty.span(),
                );
            }
        }
        _ => {
            return compile_error(
                "Must be one of i8, u8, i16, u16, i32, u32, i64, u64, f32, f64",
                ty.span(),
            );
        }
    };

    let val_str = match std::env::var(lit_str) {
        Ok(val) => val,
        Err(err) => {
            return compile_error(&format!("Failed to read environment: {}", err), lit.span())
        }
    };

    match cast_as {
        CastAs::I8 => parse::<i8>(&val_str, lit.span()),
        CastAs::U8 => parse::<u8>(&val_str, lit.span()),
        CastAs::I16 => parse::<i16>(&val_str, lit.span()),
        CastAs::U16 => parse::<u16>(&val_str, lit.span()),
        CastAs::I32 => parse::<i32>(&val_str, lit.span()),
        CastAs::U32 => parse::<u32>(&val_str, lit.span()),
        CastAs::I64 => parse::<i64>(&val_str, lit.span()),
        CastAs::U64 => parse::<u64>(&val_str, lit.span()),
        CastAs::F32 => parse::<f32>(&val_str, lit.span()),
        CastAs::F64 => parse::<f64>(&val_str, lit.span()),
    }
}

fn compile_error(msg: &str, span: Span) -> proc_macro::TokenStream {
    quote_spanned! {span=>
        compile_error!(#msg);
    }
    .into()
}

enum CastAs {
    I8,
    U8,
    I16,
    U16,
    I32,
    U32,
    I64,
    U64,
    F32,
    F64,
}

fn parse<T: FromStr + ToTokens>(str: &str, span: Span) -> proc_macro::TokenStream {
    match str.parse::<T>() {
        Ok(val) => quote! {#val}.into(),
        Err(_) => compile_error("Environment variable not parseable", span),
    }
}