1use nom::{
16 IResult, Parser,
17 branch::alt,
18 bytes::complete::{tag, tag_no_case, take_while1},
19 character::complete::{char, multispace0, multispace1, not_line_ending},
20 combinator::map,
21 multi::{many0, separated_list0},
22};
23use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27pub struct QueryFile {
28 pub queries: Vec<QueryDef>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct QueryDef {
34 pub name: String,
36 pub params: Vec<QueryParam>,
38 pub return_type: Option<ReturnType>,
39 pub body: String,
41 pub is_execute: bool,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct QueryParam {
47 pub name: String,
48 pub typ: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub enum ReturnType {
54 Single(String),
56 Vec(String),
58 Option(String),
60}
61
62impl QueryFile {
63 pub fn parse(input: &str) -> Result<Self, String> {
65 match parse_query_file(input) {
66 Ok(("", qf)) => Ok(qf),
67 Ok((remaining, _)) => Err(format!("Unexpected content: '{}'", remaining.trim())),
68 Err(e) => Err(format!("Parse error: {:?}", e)),
69 }
70 }
71
72 pub fn find_query(&self, name: &str) -> Option<&QueryDef> {
74 self.queries
75 .iter()
76 .find(|q| q.name.eq_ignore_ascii_case(name))
77 }
78
79 pub fn to_json(&self) -> Result<String, String> {
81 serde_json::to_string_pretty(self).map_err(|e| format!("JSON serialization failed: {}", e))
82 }
83
84 pub fn from_json(json: &str) -> Result<Self, String> {
86 serde_json::from_str(json).map_err(|e| format!("JSON deserialization failed: {}", e))
87 }
88}
89
90fn identifier(input: &str) -> IResult<&str, &str> {
96 take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)
97}
98
99fn ws_and_comments(input: &str) -> IResult<&str, ()> {
101 let (input, _) = many0(alt((
102 map(multispace1, |_| ()),
103 map((tag("--"), not_line_ending), |_| ()),
104 )))
105 .parse(input)?;
106 Ok((input, ()))
107}
108
109fn parse_param(input: &str) -> IResult<&str, QueryParam> {
111 let (input, _) = multispace0(input)?;
112 let (input, name) = identifier(input)?;
113 let (input, _) = multispace0(input)?;
114 let (input, _) = char(':').parse(input)?;
115 let (input, _) = multispace0(input)?;
116 let (input, typ) = identifier(input)?;
117
118 Ok((
119 input,
120 QueryParam {
121 name: name.to_string(),
122 typ: typ.to_string(),
123 },
124 ))
125}
126
127fn parse_params(input: &str) -> IResult<&str, Vec<QueryParam>> {
129 let (input, _) = char('(').parse(input)?;
130 let (input, params) = separated_list0(char(','), parse_param).parse(input)?;
131 let (input, _) = multispace0(input)?;
132 let (input, _) = char(')').parse(input)?;
133
134 Ok((input, params))
135}
136
137fn parse_return_type(input: &str) -> IResult<&str, ReturnType> {
139 let (input, _) = multispace0(input)?;
140 let (input, _) = tag("->").parse(input)?;
141 let (input, _) = multispace0(input)?;
142
143 if let Ok((input, _)) = tag::<_, _, nom::error::Error<&str>>("Vec<")(input) {
144 let (input, inner) = take_while1(|c: char| c != '>').parse(input)?;
145 let (input, _) = char('>').parse(input)?;
146 return Ok((input, ReturnType::Vec(inner.to_string())));
147 }
148
149 if let Ok((input, _)) = tag::<_, _, nom::error::Error<&str>>("Option<")(input) {
150 let (input, inner) = take_while1(|c: char| c != '>').parse(input)?;
151 let (input, _) = char('>').parse(input)?;
152 return Ok((input, ReturnType::Option(inner.to_string())));
153 }
154
155 let (input, typ) = identifier(input)?;
157 Ok((input, ReturnType::Single(typ.to_string())))
158}
159
160fn parse_body(input: &str) -> IResult<&str, &str> {
162 let (input, _) = multispace0(input)?;
163 let (input, _) = char(':').parse(input)?;
164 let (input, _) = multispace0(input)?;
165
166 let mut end = input.len();
168
169 for (i, _) in input.char_indices() {
170 if i == 0 || input.as_bytes().get(i.saturating_sub(1)) == Some(&b'\n') {
171 let line_rest = &input[i..];
173 let trimmed = line_rest.trim_start();
174 if trimmed.starts_with("query ") || trimmed.starts_with("execute ") {
175 let ws_len = line_rest.len() - trimmed.len();
177 end = i + ws_len;
178 break;
179 }
180 }
181 }
182
183 let body = input[..end].trim();
184 Ok((&input[end..], body))
185}
186
187fn parse_query_def(input: &str) -> IResult<&str, QueryDef> {
189 let (input, _) = ws_and_comments(input)?;
190
191 let (input, is_execute) = alt((
192 map(tag_no_case("query"), |_| false),
193 map(tag_no_case("execute"), |_| true),
194 ))
195 .parse(input)?;
196
197 let (input, _) = multispace1(input)?;
198 let (input, name) = identifier(input)?;
199 let (input, params) = parse_params(input)?;
200
201 let (input, return_type) = if is_execute {
203 (input, None)
204 } else {
205 let (input, rt) = parse_return_type(input)?;
206 (input, Some(rt))
207 };
208
209 let (input, body) = parse_body(input)?;
210
211 Ok((
212 input,
213 QueryDef {
214 name: name.to_string(),
215 params,
216 return_type,
217 body: body.to_string(),
218 is_execute,
219 },
220 ))
221}
222
223fn parse_query_file(input: &str) -> IResult<&str, QueryFile> {
225 let (input, _) = ws_and_comments(input)?;
226 let (input, queries) = many0(parse_query_def).parse(input)?;
227 let (input, _) = ws_and_comments(input)?;
228
229 Ok((input, QueryFile { queries }))
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_parse_simple_query() {
238 let input = r#"
239 query find_user(id: Uuid) -> User:
240 get users where id = :id
241 "#;
242
243 let qf = QueryFile::parse(input).expect("parse failed");
244 assert_eq!(qf.queries.len(), 1);
245
246 let q = &qf.queries[0];
247 assert_eq!(q.name, "find_user");
248 assert!(!q.is_execute);
249 assert_eq!(q.params.len(), 1);
250 assert_eq!(q.params[0].name, "id");
251 assert_eq!(q.params[0].typ, "Uuid");
252 assert!(matches!(q.return_type, Some(ReturnType::Single(ref t)) if t == "User"));
253 assert!(q.body.contains("get users"));
254 }
255
256 #[test]
257 fn test_parse_vec_return() {
258 let input = r#"
259 query list_orders(user_id: Uuid) -> Vec<Order>:
260 get orders where user_id = :user_id order by created_at desc
261 "#;
262
263 let qf = QueryFile::parse(input).expect("parse failed");
264 let q = &qf.queries[0];
265 assert!(matches!(q.return_type, Some(ReturnType::Vec(ref t)) if t == "Order"));
266 }
267
268 #[test]
269 fn test_parse_option_return() {
270 let input = r#"
271 query find_optional(email: String) -> Option<User>:
272 get users where email = :email limit 1
273 "#;
274
275 let qf = QueryFile::parse(input).expect("parse failed");
276 let q = &qf.queries[0];
277 assert!(matches!(q.return_type, Some(ReturnType::Option(ref t)) if t == "User"));
278 }
279
280 #[test]
281 fn test_parse_execute() {
282 let input = r#"
283 execute create_user(email: String, name: String):
284 add::users : email, name [ :email, :name ]
285 "#;
286
287 let qf = QueryFile::parse(input).expect("parse failed");
288 let q = &qf.queries[0];
289 assert!(q.is_execute);
290 assert!(q.return_type.is_none());
291 assert_eq!(q.params.len(), 2);
292 }
293
294 #[test]
295 fn test_parse_multiple_queries() {
296 let input = r#"
297 -- User queries
298 query find_user(id: Uuid) -> User:
299 get users where id = :id
300
301 query list_users() -> Vec<User>:
302 get users order by created_at desc
303
304 execute delete_user(id: Uuid):
305 del::users where id = :id
306 "#;
307
308 let qf = QueryFile::parse(input).expect("parse failed");
309 assert_eq!(qf.queries.len(), 3);
310
311 assert_eq!(qf.queries[0].name, "find_user");
312 assert_eq!(qf.queries[1].name, "list_users");
313 assert_eq!(qf.queries[2].name, "delete_user");
314 }
315}