sql_composer/parser/
mod.rs1pub mod bind;
8pub mod command;
9pub mod compose;
10pub mod template;
11
12use winnow::error::ContextError;
13use winnow::Parser;
14
15use crate::error;
16use crate::types::{Element, Template, TemplateSource};
17
18pub fn parse_template(input: &str, source: TemplateSource) -> error::Result<Template> {
22 let mut remaining = input;
23 let elements: Vec<Element> = template::template::<_, ContextError>
24 .parse_next(&mut remaining)
25 .map_err(|e| error::Error::Parse {
26 location: format!("offset {}", input.len() - remaining.len()),
27 message: e.to_string(),
28 })?;
29
30 Ok(Template { elements, source })
31}
32
33pub fn parse_template_file(path: &std::path::Path) -> error::Result<Template> {
37 let content = std::fs::read_to_string(path)?;
38 parse_template(&content, TemplateSource::File(path.to_path_buf()))
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use crate::types::{Binding, Element};
45
46 #[test]
47 fn test_parse_template_literal() {
48 let tpl = parse_template(
49 "SELECT * FROM users WHERE id = :bind(user_id);",
50 TemplateSource::Literal("test".into()),
51 )
52 .unwrap();
53
54 assert_eq!(tpl.elements.len(), 3);
55 assert_eq!(
56 tpl.elements[0],
57 Element::Sql("SELECT * FROM users WHERE id = ".into())
58 );
59 assert_eq!(
60 tpl.elements[1],
61 Element::Bind(Binding {
62 name: "user_id".into(),
63 min_values: None,
64 max_values: None,
65 nullable: false,
66 })
67 );
68 assert_eq!(tpl.elements[2], Element::Sql(";".into()));
69 }
70
71 #[test]
72 fn test_parse_template_multiline() {
73 let input = "SELECT id, name, email\nFROM users\nWHERE id = :bind(user_id)\n AND active = :bind(active);";
74 let tpl = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
75
76 let bind_count = tpl
78 .elements
79 .iter()
80 .filter(|e| matches!(e, Element::Bind(_)))
81 .count();
82 assert_eq!(bind_count, 2);
83 }
84}