env_loader_convert/
lib.rs

1use 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}