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