const_env_impl__value/
lib.rs1#![allow(non_snake_case)]
2
3use std::collections::HashMap;
4
5use proc_macro2::TokenStream;
6use quote::quote_spanned;
7use syn::{Expr, ExprLit, Lit};
8use syn::spanned::Spanned;
9
10pub trait ReadEnv {
11 fn read_env(&self, var_name: &String) -> Option<String>;
12}
13
14pub struct RealEnv;
15
16impl ReadEnv for RealEnv {
17 fn read_env(&self, var_name: &String) -> Option<String> {
18 std::env::var_os(var_name)
19 .and_then(|s| s.into_string().ok())
20 }
21}
22
23pub struct TestEnv {
24 env_vars: HashMap<String, String>
25}
26
27impl TestEnv {
28 pub fn builder() -> TestEnvBuilder {
29 TestEnvBuilder {
30 env_vars: HashMap::new()
31 }
32 }
33}
34
35impl ReadEnv for TestEnv {
36 fn read_env(&self, var_name: &String) -> Option<String> {
37 self.env_vars.get(var_name).cloned()
38 }
39}
40
41pub struct TestEnvBuilder {
42 env_vars: HashMap<String, String>
43}
44
45impl TestEnvBuilder {
46 pub fn set(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
47 self.env_vars.insert(name.into(), value.into());
48 self
49 }
50
51 pub fn build(self) -> TestEnv {
52 TestEnv {
53 env_vars: self.env_vars
54 }
55 }
56}
57
58pub fn from_env(attr: TokenStream, item: TokenStream, read_env: impl ReadEnv) -> TokenStream {
60 if let Ok(mut item_const) = syn::parse2::<syn::ItemConst>(item.clone()) {
61 let default_var_name = format!("{}", item_const.ident);
62 let var_name = extract_var_name(attr, default_var_name);
63 let var_value = match read_env.read_env(&var_name) {
64 Some(val) => val,
65 None => return item
66 };
67 let new_expr = value_to_literal(&var_value, &item_const.expr);
68 let span = item_const.span();
69 item_const.expr = Box::new(new_expr);
70 quote_spanned!(span => #item_const)
71 } else if let Ok(mut item_static) = syn::parse2::<syn::ItemStatic>(item.clone()) {
72 let default_var_name = format!("{}", item_static.ident);
73 let var_name = extract_var_name(attr, default_var_name);
74 let var_value = match read_env.read_env(&var_name) {
75 Some(val) => val,
76 None => return item
77 };
78 let new_expr = value_to_literal(&var_value, &item_static.expr);
79 let span = item_static.span();
80 item_static.expr = Box::new(new_expr);
81 quote_spanned!(span => #item_static)
82 } else {
83 panic!("TODO: error reporting");
84 }
85}
86
87pub fn value_from_env(item: TokenStream, read_env: impl ReadEnv) -> TokenStream {
89 let expr_type: syn::ExprType = syn::parse2(item)
90 .expect("Unable to parse environment variable name as expression");
91 let var_name = match &*expr_type.expr {
92 Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(var_name), .. }) => var_name.value(),
93 invalid_expr => panic!("expected a string literal containing an environment variable name but got '{:?}'", invalid_expr),
94 };
95
96 let var_value = match read_env.read_env(&var_name) {
97 Some(val) => val,
98 None => panic!("no value available for environment variable '{}'", var_name),
99 };
100
101 let span = expr_type.span();
102
103 let type_name = match &*expr_type.ty {
104 syn::Type::Path(p) => {
105 match p.path.get_ident() {
106 Some(ident) => ident.to_string(),
107 None => panic!("expected a type with no path separators but got '{:?}'", p),
108 }
109 },
110 unsupported_ty => panic!("unsupported type for environment variable substitution: {:?}", unsupported_ty),
111 };
112
113 let new_expr = match &type_name[..] {
114 "u8" | "u16" | "u32" | "u64" | "usize" |
115 "i8" | "i16" | "i32" | "i64" | "isize" => {
116 syn::parse2::<syn::LitInt>(format!("{}{}", var_value, type_name).parse().unwrap()).unwrap() },
118 unsupported_type => panic!("unsupported type for environment variable substitution: '{}'", unsupported_type),
119 };
120 quote_spanned!(span => #new_expr)
121}
122
123fn extract_var_name(attr: TokenStream, default: String) -> String {
124 if attr.is_empty() {
125 return default;
126 }
127 let expr: Expr = syn::parse2(attr)
128 .expect("Unable to parse attribute args as expression");
129 extract_var_name_from_expr(&expr)
130}
131
132fn extract_var_name_from_expr(expr: &Expr) -> String {
133 match expr {
134 Expr::Lit(literal) => {
135 match &literal.lit {
136 Lit::Str(lit_str) => {
137 lit_str.value()
138 },
139 _ => panic!("Attribute arguments are not a valid string literal")
140 }
141 },
142 Expr::Paren(paren) => {
143 extract_var_name_from_expr(&paren.expr)
144 },
145 _ => {
146 panic!("Attribute arguments are not a valid string literal expression: {:?}", expr)
147 }
148 }
149}
150
151fn value_to_literal(value: &str, original_expr: &Expr) -> Expr {
152 match original_expr {
153 Expr::Unary(_) => {
154 let new: Expr = syn::parse_str(value)
157 .expect("Failed to parse environment variable contents as valid expresion");
158 return new;
159 },
160 Expr::Lit(literal) => {
161 let new_lit = match &literal.lit {
162 Lit::Str(original) => {
163 let mut new: syn::LitStr = syn::parse_str(&format!("\"{}\"", value))
164 .expect("Failed to parse environment variable contents as literal string");
165 new.set_span(original.span());
166 Lit::Str(new)
167 },
168 Lit::ByteStr(original) => {
169 let mut new: syn::LitByteStr = syn::parse_str(&format!("b\"{}\"", value))
170 .expect("Failed to parse environment variable contents as literal byte string");
171 new.set_span(original.span());
172 Lit::ByteStr(new)
173 },
174 Lit::Byte(original) => {
175 let mut new: syn::LitByte = syn::parse_str(&format!("b'{}'", value))
176 .expect("Failed to parse environment variable contents as literal byte");
177 new.set_span(original.span());
178 Lit::Byte(new)
179 },
180 Lit::Char(original) => {
181 let mut new: syn::LitChar = syn::parse_str(&format!("'{}'", value))
182 .expect("Failed to parse environment variable contents as literal character");
183 new.set_span(original.span());
184 Lit::Char(new)
185 },
186 Lit::Bool(_) | Lit::Int(_) | Lit::Float(_) | Lit::Verbatim(_) => {
189 let new: Expr = syn::parse_str(value)
190 .expect("Failed to parse environment variable contents as valid expression");
191 return new;
192 }
193 };
194 ExprLit {
195 attrs: literal.attrs.clone(),
196 lit: new_lit
197 }.into()
198 },
199 _ => panic!("Original const expression was not a recognized literal expression")
200 }
201}