prax_schema/ast/
generator.rs1use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5use smol_str::SmolStr;
6
7use super::Span;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct Generator {
22 pub name: SmolStr,
24 pub provider: Option<SmolStr>,
26 pub output: Option<SmolStr>,
28 pub generate: GeneratorToggle,
30 pub properties: IndexMap<SmolStr, GeneratorValue>,
32 pub span: Span,
34}
35
36impl Generator {
37 pub fn new(name: impl Into<SmolStr>, span: Span) -> Self {
38 Self {
39 name: name.into(),
40 provider: None,
41 output: None,
42 generate: GeneratorToggle::Always,
43 properties: IndexMap::new(),
44 span,
45 }
46 }
47
48 pub fn is_enabled(&self) -> bool {
50 match &self.generate {
51 GeneratorToggle::Always => true,
52 GeneratorToggle::Never => false,
53 GeneratorToggle::Literal(val) => *val,
54 GeneratorToggle::Env(var_name) => std::env::var(var_name)
55 .map(|v| {
56 let v = v.trim().to_lowercase();
57 v == "true" || v == "1" || v == "yes"
58 })
59 .unwrap_or(false),
60 }
61 }
62
63 pub fn name(&self) -> &str {
64 &self.name
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70pub enum GeneratorToggle {
71 Always,
73 Never,
75 Literal(bool),
77 Env(SmolStr),
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
83pub enum GeneratorValue {
84 String(SmolStr),
86 Bool(bool),
88 Env(SmolStr),
90 Ident(SmolStr),
92}
93
94impl GeneratorValue {
95 pub fn resolve(&self) -> Option<String> {
97 match self {
98 Self::String(s) => Some(s.to_string()),
99 Self::Bool(b) => Some(b.to_string()),
100 Self::Ident(s) => Some(s.to_string()),
101 Self::Env(var) => std::env::var(var.as_str()).ok(),
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 fn span() -> Span {
111 Span::new(0, 10)
112 }
113
114 #[test]
115 fn test_generator_new() {
116 let g = Generator::new("typescript", span());
117 assert_eq!(g.name(), "typescript");
118 assert!(g.provider.is_none());
119 assert!(g.output.is_none());
120 assert!(g.is_enabled());
121 }
122
123 #[test]
124 fn test_generator_toggle_always() {
125 let g = Generator::new("test", span());
126 assert!(g.is_enabled());
127 }
128
129 #[test]
130 fn test_generator_toggle_literal_true() {
131 let mut g = Generator::new("test", span());
132 g.generate = GeneratorToggle::Literal(true);
133 assert!(g.is_enabled());
134 }
135
136 #[test]
137 fn test_generator_toggle_literal_false() {
138 let mut g = Generator::new("test", span());
139 g.generate = GeneratorToggle::Literal(false);
140 assert!(!g.is_enabled());
141 }
142
143 #[test]
144 fn test_generator_toggle_never() {
145 let mut g = Generator::new("test", span());
146 g.generate = GeneratorToggle::Never;
147 assert!(!g.is_enabled());
148 }
149
150 #[test]
151 fn test_generator_toggle_env_true() {
152 unsafe { std::env::set_var("PRAX_TEST_GEN_TOGGLE", "true") };
153 let mut g = Generator::new("test", span());
154 g.generate = GeneratorToggle::Env("PRAX_TEST_GEN_TOGGLE".into());
155 assert!(g.is_enabled());
156 unsafe { std::env::remove_var("PRAX_TEST_GEN_TOGGLE") };
157 }
158
159 #[test]
160 fn test_generator_toggle_env_false() {
161 unsafe { std::env::set_var("PRAX_TEST_GEN_TOGGLE_F", "false") };
162 let mut g = Generator::new("test", span());
163 g.generate = GeneratorToggle::Env("PRAX_TEST_GEN_TOGGLE_F".into());
164 assert!(!g.is_enabled());
165 unsafe { std::env::remove_var("PRAX_TEST_GEN_TOGGLE_F") };
166 }
167
168 #[test]
169 fn test_generator_toggle_env_missing() {
170 let mut g = Generator::new("test", span());
171 g.generate = GeneratorToggle::Env("PRAX_TEST_NONEXISTENT_VAR_999".into());
172 assert!(!g.is_enabled());
173 }
174
175 #[test]
176 fn test_generator_toggle_env_one() {
177 unsafe { std::env::set_var("PRAX_TEST_GEN_ONE", "1") };
178 let mut g = Generator::new("test", span());
179 g.generate = GeneratorToggle::Env("PRAX_TEST_GEN_ONE".into());
180 assert!(g.is_enabled());
181 unsafe { std::env::remove_var("PRAX_TEST_GEN_ONE") };
182 }
183
184 #[test]
185 fn test_generator_value_resolve_string() {
186 let v = GeneratorValue::String("hello".into());
187 assert_eq!(v.resolve(), Some("hello".to_string()));
188 }
189
190 #[test]
191 fn test_generator_value_resolve_bool() {
192 let v = GeneratorValue::Bool(true);
193 assert_eq!(v.resolve(), Some("true".to_string()));
194 }
195
196 #[test]
197 fn test_generator_value_resolve_env() {
198 unsafe { std::env::set_var("PRAX_TEST_VAL", "resolved") };
199 let v = GeneratorValue::Env("PRAX_TEST_VAL".into());
200 assert_eq!(v.resolve(), Some("resolved".to_string()));
201 unsafe { std::env::remove_var("PRAX_TEST_VAL") };
202 }
203
204 #[test]
205 fn test_generator_value_resolve_env_missing() {
206 let v = GeneratorValue::Env("PRAX_TEST_MISSING_VAL_999".into());
207 assert_eq!(v.resolve(), None);
208 }
209
210 #[test]
211 fn test_parse_generator_block() {
212 use crate::parse_schema;
213
214 let schema = parse_schema(
215 r#"
216 generator typescript {
217 provider = "prax-typegen"
218 output = "./src/types"
219 }
220 "#,
221 )
222 .unwrap();
223
224 assert_eq!(schema.generators.len(), 1);
225 let g = schema.get_generator("typescript").unwrap();
226 assert_eq!(g.provider.as_deref(), Some("prax-typegen"));
227 assert_eq!(g.output.as_deref(), Some("./src/types"));
228 assert!(g.is_enabled());
229 }
230
231 #[test]
232 fn test_parse_generator_with_env_toggle() {
233 use crate::parse_schema;
234
235 let schema = parse_schema(
236 r#"
237 generator typescript {
238 provider = "prax-typegen"
239 output = "./src/types"
240 generate = env("PRAX_TEST_PARSE_GEN_TOGGLE")
241 }
242 "#,
243 )
244 .unwrap();
245
246 let g = schema.get_generator("typescript").unwrap();
247 assert_eq!(
248 g.generate,
249 GeneratorToggle::Env("PRAX_TEST_PARSE_GEN_TOGGLE".into())
250 );
251
252 assert!(!g.is_enabled());
253
254 unsafe { std::env::set_var("PRAX_TEST_PARSE_GEN_TOGGLE", "true") };
255 assert!(g.is_enabled());
256 unsafe { std::env::remove_var("PRAX_TEST_PARSE_GEN_TOGGLE") };
257 }
258
259 #[test]
260 fn test_parse_generator_with_bool_toggle() {
261 use crate::parse_schema;
262
263 let schema = parse_schema(
264 r#"
265 generator disabled {
266 provider = "some-provider"
267 generate = false
268 }
269 "#,
270 )
271 .unwrap();
272
273 let g = schema.get_generator("disabled").unwrap();
274 assert_eq!(g.generate, GeneratorToggle::Literal(false));
275 assert!(!g.is_enabled());
276 }
277
278 #[test]
279 fn test_parse_multiple_generators() {
280 use crate::parse_schema;
281
282 let schema = parse_schema(
283 r#"
284 generator typescript {
285 provider = "prax-typegen"
286 output = "./ts"
287 }
288
289 generator python {
290 provider = "prax-pygen"
291 output = "./py"
292 generate = env("PYTHON_GENERATE")
293 }
294 "#,
295 )
296 .unwrap();
297
298 assert_eq!(schema.generators.len(), 2);
299 assert!(schema.get_generator("typescript").is_some());
300 assert!(schema.get_generator("python").is_some());
301 }
302
303 #[test]
304 fn test_enabled_generators_filters() {
305 use crate::parse_schema;
306
307 let schema = parse_schema(
308 r#"
309 generator enabled_one {
310 provider = "a"
311 generate = true
312 }
313
314 generator disabled_one {
315 provider = "b"
316 generate = false
317 }
318 "#,
319 )
320 .unwrap();
321
322 let enabled = schema.enabled_generators();
323 assert_eq!(enabled.len(), 1);
324 assert_eq!(enabled[0].name(), "enabled_one");
325 }
326
327 #[test]
328 fn test_parse_generator_extra_properties() {
329 use crate::parse_schema;
330
331 let schema = parse_schema(
332 r#"
333 generator typescript {
334 provider = "prax-typegen"
335 output = "./src/types"
336 emitZod = true
337 packageName = "@myapp/types"
338 }
339 "#,
340 )
341 .unwrap();
342
343 let g = schema.get_generator("typescript").unwrap();
344 assert_eq!(
345 g.properties.get("emitZod"),
346 Some(&GeneratorValue::Bool(true))
347 );
348 assert_eq!(
349 g.properties.get("packageName"),
350 Some(&GeneratorValue::String("@myapp/types".into()))
351 );
352 }
353}