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