env_loader_convert/
lib.rs1use proc_macro::TokenStream;
2use quote::quote;
3use std::str::FromStr;
4mod validators;
5use validators::{validate_bool_constraints, validate_num_constraints, Constraint};
6struct KeyValue {
7 key: String,
8 ty: String,
9 constraints: Option<Vec<Constraint>>,
10}
11
12impl FromStr for KeyValue {
13 type Err = ();
14
15 fn from_str(s: &str) -> Result<Self, Self::Err> {
16 let parts: Vec<&str> = s.split("=>").collect();
17
18 let kv_parts: Vec<&str> = parts[0].split(':').collect();
19
20 if kv_parts.len() != 2 {
21 return Err(());
22 }
23 let key = kv_parts[0].trim().to_string();
24 let ty = kv_parts[1].trim().to_string();
25
26 if parts.len() != 2 {
27 return Ok(KeyValue {
28 key,
29 ty,
30 constraints: None,
31 });
32 }
33
34 let splitted_constraints: Vec<String> = parts[1]
35 .trim()
36 .split_ascii_whitespace()
37 .map(|cons| cons.trim().to_string())
38 .collect();
39
40 let mut constraints = vec![];
41
42 for constraint in splitted_constraints {
43 if constraint.starts_with("min(") && constraint.ends_with(')') {
44 let value = constraint[4..constraint.len() - 1].trim().parse::<i64>();
45 if value.is_err() {
46 panic!("Wrong value for constraint Min provided");
47 }
48 constraints.push(Constraint::Min(value.unwrap()));
49 continue;
50 } else if constraint.starts_with("max(") && constraint.ends_with(')') {
51 let value = constraint[4..constraint.len() - 1].trim().parse::<i64>();
52 if value.is_err() {
53 panic!("Wrong value for constraint Max provided");
54 }
55 constraints.push(Constraint::Max(value.unwrap()));
56 continue;
57 } else if constraint.to_ascii_lowercase() == "notempty" {
58 constraints.push(Constraint::NotEmpty);
59 continue;
60 } else if constraint.to_ascii_lowercase() == "optional" {
61 constraints.push(Constraint::Optional);
62 continue;
63 } else if constraint.starts_with("len(") && constraint.ends_with(')') {
64 let value = constraint[4..constraint.len() - 1].trim().parse::<usize>();
65 if value.is_err() {
66 panic!("Wrong value for constraint Len provided");
67 }
68 constraints.push(Constraint::Len(value.unwrap()));
69 continue;
70 } else {
71 panic!("Wrong constraint syntax {key}: {constraint}.");
72 }
73 }
74
75 Ok(KeyValue {
76 key,
77 ty,
78 constraints: Some(constraints),
79 })
80 }
81}
82
83fn generate_mask(constraints: &Option<Vec<Constraint>>) -> String {
84 let constraints = constraints.as_ref().unwrap();
85 let mut mask = [
86 "".to_string(),
87 "".to_string(),
88 "".to_string(),
89 "".to_string(),
90 "".to_string(),
91 "".to_string(),
92 ];
93 for cons in constraints {
94 match cons {
95 Constraint::Max(val) => mask[0].push_str(&format!("{val}")),
96 Constraint::Min(val) => mask[1].push_str(&format!("{val}")),
97 Constraint::Optional => mask[2].push('1'),
98 Constraint::NotEmpty => mask[3].push('1'),
99
100 Constraint::Len(len) => mask[4].push_str(&format!("{len}")),
101 }
102 }
103
104 mask.join(",")
105}
106
107#[proc_macro]
108pub fn convert(input: TokenStream) -> TokenStream {
109 let input = input.to_string();
110 let input = input.trim();
111 let input = input.strip_prefix('{').unwrap_or(input);
112 let input = input.strip_suffix('}').unwrap_or(input);
113 let input = input.trim();
114
115 let key_values: Vec<KeyValue> = input
116 .split(',')
117 .map(|s| s.trim().parse())
118 .collect::<Result<Vec<KeyValue>, _>>()
119 .unwrap_or_else(|_| panic!("Invalid input: {:?}", input));
120
121 let result = key_values.iter().map(
122 |KeyValue {
123 key,
124 ty,
125 constraints,
126 }| {
127 let key_str = key.as_str();
128 let typing = match ty.to_ascii_lowercase().as_str() {
129 "int" | "integer" | "i32" => {
130 if constraints.is_some() {
131 validate_num_constraints(key, constraints.as_ref().unwrap());
132 }
133
134 quote! { Value::Int }
135 }
136 "str" | "string" => {
137 quote! { Value::Str }
138 }
139 "long" | "i64" => {
140 if constraints.is_some() {
141 validate_num_constraints(key, constraints.as_ref().unwrap());
142 }
143 quote! {Value::Long }
144 }
145 "bool" | "boolean" => {
146 if constraints.is_some() {
147 validate_bool_constraints(key, constraints.as_ref().unwrap());
148 }
149 quote! { Value::Bool }
150 }
151 _ => panic!("Unsupported type: {:?}", ty),
152 };
153
154 if constraints.is_none() {
155 quote! { (#key_str, #typing, "".to_string() ) }
156 } else {
157 let mask = generate_mask(constraints);
158 quote! { (#key_str, #typing, #mask.to_string() ) }
159 }
160 },
161 );
162
163 let output = quote! { [ #( #result ),* ] };
164
165 output.into()
166}