1use nom::{
16 branch::alt,
17 bytes::complete::{tag, tag_no_case, take_while1},
18 character::complete::{multispace0, multispace1, char, not_line_ending},
19 combinator::map,
20 multi::{many0, separated_list0},
21 Parser,
22 IResult,
23};
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
28pub struct QueryFile {
29 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,
51 pub typ: String,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub enum ReturnType {
57 Single(String),
59 Vec(String),
61 Option(String),
63}
64
65impl QueryFile {
66 pub fn parse(input: &str) -> Result<Self, String> {
68 match parse_query_file(input) {
69 Ok(("", qf)) => Ok(qf),
70 Ok((remaining, _)) => Err(format!("Unexpected content: '{}'", remaining.trim())),
71 Err(e) => Err(format!("Parse error: {:?}", e)),
72 }
73 }
74
75 pub fn find_query(&self, name: &str) -> Option<&QueryDef> {
77 self.queries.iter().find(|q| q.name.eq_ignore_ascii_case(name))
78 }
79
80 pub fn to_json(&self) -> Result<String, String> {
82 serde_json::to_string_pretty(self)
83 .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)
89 .map_err(|e| format!("JSON deserialization failed: {}", e))
90 }
91}
92
93fn identifier(input: &str) -> IResult<&str, &str> {
99 take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)
100}
101
102fn ws_and_comments(input: &str) -> IResult<&str, ()> {
104 let (input, _) = many0(alt((
105 map(multispace1, |_| ()),
106 map((tag("--"), not_line_ending), |_| ()),
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((input, QueryParam {
121 name: name.to_string(),
122 typ: typ.to_string(),
123 }))
124}
125
126fn parse_params(input: &str) -> IResult<&str, Vec<QueryParam>> {
128 let (input, _) = char('(').parse(input)?;
129 let (input, params) = separated_list0(
130 char(','),
131 parse_param,
132 ).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 )).parse(input)?;
199
200 let (input, _) = multispace1(input)?;
201 let (input, name) = identifier(input)?;
202 let (input, params) = parse_params(input)?;
203
204 let (input, return_type) = if is_execute {
206 (input, None)
207 } else {
208 let (input, rt) = parse_return_type(input)?;
209 (input, Some(rt))
210 };
211
212 let (input, body) = parse_body(input)?;
213
214 Ok((input, QueryDef {
215 name: name.to_string(),
216 params,
217 return_type,
218 body: body.to_string(),
219 is_execute,
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}