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