1use crate::ast::*;
2use crate::error::{ParseError, Result};
3use crate::grammar::{RohasParser, Rule};
4use pest::Parser as PestParser;
5use std::fs;
6use std::path::Path;
7use tracing::{debug, info};
8
9pub struct Parser;
10
11impl Parser {
12 pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Schema> {
13 let path = path.as_ref();
14 info!("Parsing schema file: {}", path.display());
15
16 let content = fs::read_to_string(path)
17 .map_err(|e| ParseError::FileNotFound(format!("{}: {}", path.display(), e)))?;
18
19 Self::parse_string(&content)
20 }
21
22 pub fn parse_string(input: &str) -> Result<Schema> {
23 let pairs = RohasParser::parse(Rule::schema, input)?;
24 let mut schema = Schema::new();
25
26 for pair in pairs {
27 if pair.as_rule() == Rule::schema {
28 for inner_pair in pair.into_inner() {
29 match inner_pair.as_rule() {
30 Rule::model => {
31 let model = Self::parse_model(inner_pair)?;
32 schema.models.push(model);
33 }
34 Rule::api => {
35 let api = Self::parse_api(inner_pair)?;
36 schema.apis.push(api);
37 }
38 Rule::event => {
39 let event = Self::parse_event(inner_pair)?;
40 schema.events.push(event);
41 }
42 Rule::cron => {
43 let cron = Self::parse_cron(inner_pair)?;
44 schema.crons.push(cron);
45 }
46 Rule::input => {
47 let input = Self::parse_input(inner_pair)?;
48 schema.inputs.push(input);
49 }
50 Rule::ws => {
51 let ws = Self::parse_websocket(inner_pair)?;
52 schema.websockets.push(ws);
53 }
54 Rule::EOI => {}
55 _ => {
56 debug!("Unexpected rule: {:?}", inner_pair.as_rule());
57 }
58 }
59 }
60 }
61 }
62
63 schema.validate()?;
64 Ok(schema)
65 }
66
67 fn parse_model(pair: pest::iterators::Pair<Rule>) -> Result<Model> {
68 let mut inner = pair.into_inner();
69 let name = inner
70 .next()
71 .ok_or_else(|| ParseError::InvalidModel("Missing model name".into()))?
72 .as_str()
73 .to_string();
74
75 let mut fields = Vec::new();
76
77 for field_pair in inner {
78 if field_pair.as_rule() == Rule::field {
79 fields.push(Self::parse_field(field_pair)?);
80 }
81 }
82
83 Ok(Model {
84 name,
85 fields,
86 attributes: Vec::new(),
87 })
88 }
89
90 fn parse_field(pair: pest::iterators::Pair<Rule>) -> Result<Field> {
91 let mut inner = pair.into_inner();
92
93 let name = inner
94 .next()
95 .ok_or_else(|| ParseError::InvalidModel("Missing field name".into()))?
96 .as_str()
97 .to_string();
98
99 let field_type_pair = inner
100 .next()
101 .ok_or_else(|| ParseError::InvalidModel("Missing field type".into()))?;
102
103 let field_type = Self::parse_field_type(field_type_pair)?;
104
105 let mut optional = false;
106 let mut attributes = Vec::new();
107
108 for item in inner {
109 match item.as_rule() {
110 Rule::optional => optional = true,
111 Rule::attribute => attributes.push(Self::parse_attribute(item)?),
112 _ => {}
113 }
114 }
115
116 Ok(Field {
117 name,
118 field_type,
119 optional,
120 attributes,
121 })
122 }
123
124 fn parse_field_type(pair: pest::iterators::Pair<Rule>) -> Result<FieldType> {
125 let mut inner = pair.into_inner();
126 let type_name = inner
127 .next()
128 .ok_or_else(|| ParseError::InvalidType("Missing type name".into()))?
129 .as_str();
130
131 let mut field_type = FieldType::from_str(type_name);
132
133 if let Some(array_pair) = inner.next() {
135 if array_pair.as_rule() == Rule::array_suffix {
136 field_type = FieldType::Array(Box::new(field_type));
137 }
138 }
139
140 Ok(field_type)
141 }
142
143 fn parse_attribute(pair: pest::iterators::Pair<Rule>) -> Result<Attribute> {
144 let mut inner = pair.into_inner();
145 let name = inner
146 .next()
147 .ok_or_else(|| ParseError::InvalidAttribute("Missing attribute name".into()))?
148 .as_str()
149 .to_string();
150
151 let mut args = Vec::new();
152
153 for arg_pair in inner {
154 if arg_pair.as_rule() == Rule::attr_args {
155 for arg in arg_pair.into_inner() {
156 if arg.as_rule() == Rule::attr_arg_list {
157 for item in arg.into_inner() {
158 args.push(item.as_str().trim_matches('"').to_string());
159 }
160 }
161 }
162 }
163 }
164
165 Ok(Attribute { name, args })
166 }
167
168 fn parse_api(pair: pest::iterators::Pair<Rule>) -> Result<Api> {
169 let mut inner = pair.into_inner();
170 let name = inner
171 .next()
172 .ok_or_else(|| ParseError::InvalidApi("Missing API name".into()))?
173 .as_str()
174 .to_string();
175
176 let mut method = None;
177 let mut path = None;
178 let mut body = None;
179 let mut response = None;
180 let mut triggers = Vec::new();
181 let mut middlewares = Vec::new();
182
183 for prop in inner {
184 if prop.as_rule() == Rule::api_property {
185 let prop_text = prop.as_str();
186 let mut prop_inner = prop.into_inner();
187
188 if let Some(key) = prop_inner.next() {
189 match key.as_rule() {
190 Rule::http_method => method = HttpMethod::from_str(key.as_str()),
191 Rule::string => path = Some(key.as_str().trim_matches('"').to_string()),
192 Rule::ident => {
193 if prop_text.starts_with("body:") {
194 body = Some(key.as_str().to_string());
195 } else if prop_text.starts_with("response:") {
196 response = Some(key.as_str().to_string());
197 }
198 }
199 Rule::trigger_list => {
200 triggers = Self::parse_string_list(key)?;
201 }
202 Rule::string_list | Rule::middleware_list => {
203 middlewares = Self::parse_string_list(key)?;
204 }
205 _ => {}
206 }
207 }
208 }
209 }
210
211 Ok(Api {
212 name,
213 method: method.ok_or_else(|| ParseError::InvalidApi("Missing HTTP method".into()))?,
214 path: path.ok_or_else(|| ParseError::InvalidApi("Missing path".into()))?,
215 body,
216 response: response.ok_or_else(|| ParseError::InvalidApi("Missing response".into()))?,
217 triggers,
218 middlewares,
219 })
220 }
221
222 fn parse_event(pair: pest::iterators::Pair<Rule>) -> Result<Event> {
223 let mut inner = pair.into_inner();
224 let name = inner
225 .next()
226 .ok_or_else(|| ParseError::InvalidEvent("Missing event name".into()))?
227 .as_str()
228 .to_string();
229
230 let mut payload = String::new();
231 let mut handlers = Vec::new();
232 let mut triggers = Vec::new();
233 let mut adapter_type = None;
234
235 for prop in inner {
236 if prop.as_rule() == Rule::event_property {
237 let prop_text = prop.as_str();
238 let mut prop_inner = prop.into_inner();
239 if let Some(value) = prop_inner.next() {
240 match value.as_rule() {
241 Rule::ident => {
242 if prop_text.starts_with("payload:") {
243 payload = value.as_str().to_string();
244 } else if prop_text.starts_with("type:") {
245 let type_str = value.as_str().to_lowercase();
246 if type_str == "sqs" || type_str == "eventbridge" {
247 adapter_type = Some(type_str);
248 } else {
249 return Err(ParseError::InvalidEvent(format!(
250 "Invalid adapter type '{}' for event '{}'. Must be 'sqs' or 'eventbridge'",
251 value.as_str(), name
252 )));
253 }
254 }
255 }
256 Rule::handler_list | Rule::trigger_list => {
257 let items = Self::parse_string_list(value)?;
258 if prop_text.starts_with("handler:") {
259 handlers = items;
260 } else if prop_text.starts_with("triggers:") {
261 triggers = items;
262 }
263 }
264 _ => {}
265 }
266 }
267 }
268 }
269
270 Ok(Event {
271 name,
272 payload,
273 handlers,
274 triggers,
275 adapter_type,
276 })
277 }
278
279 fn parse_cron(pair: pest::iterators::Pair<Rule>) -> Result<Cron> {
280 let mut inner = pair.into_inner();
281 let name = inner
282 .next()
283 .ok_or_else(|| ParseError::InvalidCron("Missing cron name".into()))?
284 .as_str()
285 .to_string();
286
287 let mut schedule = String::new();
288 let mut triggers = Vec::new();
289
290 for prop in inner {
291 if prop.as_rule() == Rule::cron_property {
292 let mut prop_inner = prop.into_inner();
293 if let Some(value) = prop_inner.next() {
294 match value.as_rule() {
295 Rule::string => schedule = value.as_str().trim_matches('"').to_string(),
296 Rule::trigger_list => triggers = Self::parse_string_list(value)?,
297 _ => {}
298 }
299 }
300 }
301 }
302
303 Ok(Cron {
304 name,
305 schedule,
306 triggers,
307 })
308 }
309
310 fn parse_input(pair: pest::iterators::Pair<Rule>) -> Result<Input> {
311 let mut inner = pair.into_inner();
312 let name = inner
313 .next()
314 .ok_or_else(|| ParseError::InvalidModel("Missing input name".into()))?
315 .as_str()
316 .to_string();
317
318 let mut fields = Vec::new();
319
320 for field_pair in inner {
321 if field_pair.as_rule() == Rule::input_field {
322 let mut field_inner = field_pair.into_inner();
323
324 let field_name = field_inner
325 .next()
326 .ok_or_else(|| ParseError::InvalidModel("Missing field name".into()))?
327 .as_str()
328 .to_string();
329
330 let field_type_pair = field_inner
331 .next()
332 .ok_or_else(|| ParseError::InvalidModel("Missing field type".into()))?;
333
334 let field_type = Self::parse_field_type(field_type_pair)?;
335
336 let optional = field_inner.next().is_some();
337
338 fields.push(Field {
339 name: field_name,
340 field_type,
341 optional,
342 attributes: Vec::new(),
343 });
344 }
345 }
346
347 Ok(Input { name, fields })
348 }
349
350 fn parse_string_list(pair: pest::iterators::Pair<Rule>) -> Result<Vec<String>> {
351 let mut items = Vec::new();
352 for item in pair.into_inner() {
353 match item.as_rule() {
354 Rule::ident => items.push(item.as_str().to_string()),
355 Rule::string => items.push(item.as_str().trim_matches('"').to_string()),
356 _ => {}
357 }
358 }
359 Ok(items)
360 }
361
362 fn parse_websocket(pair: pest::iterators::Pair<Rule>) -> Result<WebSocket> {
363 let mut inner = pair.into_inner();
364 let name = inner
365 .next()
366 .ok_or_else(|| ParseError::InvalidApi("Missing websocket name".into()))?
367 .as_str()
368 .to_string();
369
370 let mut path = None;
371 let mut message = None;
372 let mut on_connect = Vec::new();
373 let mut on_message = Vec::new();
374 let mut on_disconnect = Vec::new();
375 let mut triggers = Vec::new();
376 let mut broadcast = false;
377 let mut middlewares = Vec::new();
378
379 for prop in inner {
380 if prop.as_rule() == Rule::ws_property {
381 let prop_text = prop.as_str();
382 let mut prop_inner = prop.into_inner();
383
384 if let Some(key) = prop_inner.next() {
385 match key.as_rule() {
386 Rule::string => {
387 if prop_text.starts_with("path:") {
388 path = Some(key.as_str().trim_matches('"').to_string());
389 }
390 }
391 Rule::ident => {
392 if prop_text.starts_with("message:") {
393 message = Some(key.as_str().to_string());
394 }
395 }
396 Rule::handler_list => {
397 if prop_text.starts_with("onConnect:") {
398 on_connect = Self::parse_string_list(key)?;
399 } else if prop_text.starts_with("onMessage:") {
400 on_message = Self::parse_string_list(key)?;
401 } else if prop_text.starts_with("onDisconnect:") {
402 on_disconnect = Self::parse_string_list(key)?;
403 }
404 }
405 Rule::trigger_list => {
406 triggers = Self::parse_string_list(key)?;
407 }
408 Rule::string_list | Rule::middleware_list => {
409 if prop_text.starts_with("middlewares:") {
410 middlewares = Self::parse_string_list(key)?;
411 }
412 }
413 Rule::boolean => {
414 if prop_text.starts_with("broadcast:") {
415 broadcast = key.as_str() == "true";
416 }
417 }
418 _ => {}
419 }
420 }
421 }
422 }
423
424 Ok(WebSocket {
425 name,
426 path: path.ok_or_else(|| ParseError::InvalidApi("Missing path".into()))?,
427 message,
428 on_connect,
429 on_message,
430 on_disconnect,
431 triggers,
432 broadcast,
433 middlewares,
434 })
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn test_parse_model() {
444 let input = r#"
445 model User {
446 id Int @id @auto
447 name String
448 email String @unique
449 }
450 "#;
451
452 let schema = Parser::parse_string(input).expect("Failed to parse");
453 assert_eq!(schema.models.len(), 1);
454 assert_eq!(schema.models[0].name, "User");
455 assert_eq!(schema.models[0].fields.len(), 3);
456 }
457
458 #[test]
459 fn test_parse_api() {
460 let input = r#"
461 api CreateUser {
462 method: POST
463 path: "/users"
464 body: CreateUserInput
465 response: User
466 triggers: [UserCreated]
467 }
468 "#;
469
470 let schema = Parser::parse_string(input).expect("Failed to parse");
471 assert_eq!(schema.apis.len(), 1);
472 assert_eq!(schema.apis[0].name, "CreateUser");
473 }
474
475 #[test]
476 fn test_parse_event() {
477 let input = r#"
478 event UserCreated {
479 payload: User
480 handler: [send_welcome_email, update_analytics]
481 triggers: [NotifyAdmin]
482 }
483 "#;
484
485 let schema = Parser::parse_string(input).expect("Failed to parse");
486 assert_eq!(schema.events.len(), 1);
487 assert_eq!(schema.events[0].name, "UserCreated");
488 assert_eq!(schema.events[0].handlers.len(), 2);
489 }
490}