ryo_query_language/
parser.rs1use crate::schema::{MatchAttrs, NameMatcher, Query, QueryKind, ViewMode};
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum ParseError {
9 #[error("YAML parse error: {0}")]
11 Yaml(#[from] serde_yaml::Error),
12
13 #[error("JSON parse error: {0}")]
15 Json(#[from] serde_json::Error),
16
17 #[error("Invalid query: {0}")]
19 Invalid(String),
20
21 #[error("Unknown kind: {0}")]
23 UnknownKind(String),
24}
25
26pub struct QueryParser;
28
29impl QueryParser {
30 pub fn from_yaml(yaml: &str) -> Result<Query, ParseError> {
32 let query: Query = serde_yaml::from_str(yaml)?;
33 Self::validate(&query)?;
34 Ok(query)
35 }
36
37 pub fn from_json(json: &str) -> Result<Query, ParseError> {
39 let query: Query = serde_json::from_str(json)?;
40 Self::validate(&query)?;
41 Ok(query)
42 }
43
44 pub fn parse(input: &str) -> Result<Query, ParseError> {
46 let trimmed = input.trim();
48 if trimmed.starts_with('{') {
49 return Self::from_json(input);
50 }
51 Self::from_yaml(input)
52 }
53
54 pub fn from_cli(kind_str: &str, pattern: &str) -> Result<Query, ParseError> {
60 let kind = Self::parse_kind(kind_str)?;
61 let name_matcher = Self::parse_name_pattern(pattern);
62
63 Ok(Query {
64 kind,
65 r#match: Some(MatchAttrs {
66 name: Some(name_matcher),
67 ..Default::default()
68 }),
69 inner: vec![],
70 queries: vec![],
71 name: None,
72 resolve: None,
73 scope: None,
74 view: None,
75 limit: None,
76 body: None,
77 relations: None,
78 })
79 }
80
81 fn parse_kind(s: &str) -> Result<QueryKind, ParseError> {
83 match s.to_lowercase().as_str() {
84 "any" | "*" => Ok(QueryKind::Any),
85 "fn" | "function" => Ok(QueryKind::Function),
86 "struct" => Ok(QueryKind::Struct),
87 "enum" => Ok(QueryKind::Enum),
88 "trait" => Ok(QueryKind::Trait),
89 "impl" => Ok(QueryKind::Impl),
90 "mod" | "module" => Ok(QueryKind::Mod),
91 "const" => Ok(QueryKind::Const),
92 "static" => Ok(QueryKind::Static),
93 "type" | "typealias" => Ok(QueryKind::TypeAlias),
94 _ => Err(ParseError::UnknownKind(s.to_string())),
95 }
96 }
97
98 fn parse_name_pattern(pattern: &str) -> NameMatcher {
105 if pattern.contains('*') || pattern.contains('?') {
106 NameMatcher::Detailed(crate::schema::NameMatcherDetailed {
108 glob: Some(pattern.to_string()),
109 contains: None,
110 starts_with: None,
111 ends_with: None,
112 regex: None,
113 ignore_case: None,
114 ignore_word_separate: None,
115 })
116 } else {
117 NameMatcher::Exact(pattern.to_string())
118 }
119 }
120
121 fn validate(query: &Query) -> Result<(), ParseError> {
123 match query.kind {
124 QueryKind::Or | QueryKind::And => {
125 if query.queries.is_empty() {
126 return Err(ParseError::Invalid(
127 "Or/And query requires 'queries' field".to_string(),
128 ));
129 }
130 for q in &query.queries {
131 Self::validate(q)?;
132 }
133 }
134 QueryKind::Pattern => {
135 if query.name.is_none() {
136 return Err(ParseError::Invalid(
137 "Pattern query requires 'name' field".to_string(),
138 ));
139 }
140 }
141 _ => {
142 for q in &query.inner {
144 Self::validate(q)?;
145 }
146 }
147 }
148 Ok(())
149 }
150}
151
152pub fn parse_view_mode(s: &str) -> Option<ViewMode> {
154 match s.to_lowercase().as_str() {
155 "snippet" => Some(ViewMode::Snippet),
156 "precise" => Some(ViewMode::Precise),
157 "count" => Some(ViewMode::Count),
158 "def" => Some(ViewMode::Def),
159 _ => None,
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_parse_yaml() {
169 let yaml = r#"
170kind: Function
171match:
172 name: "process"
173"#;
174 let query = QueryParser::from_yaml(yaml).unwrap();
175 assert_eq!(query.kind, QueryKind::Function);
176 }
177
178 #[test]
179 fn test_parse_json() {
180 let json = r#"{"kind": "Struct", "match": {"name": "Config"}}"#;
181 let query = QueryParser::from_json(json).unwrap();
182 assert_eq!(query.kind, QueryKind::Struct);
183 }
184
185 #[test]
186 fn test_auto_detect_json() {
187 let json = r#"{"kind": "Enum"}"#;
188 let query = QueryParser::parse(json).unwrap();
189 assert_eq!(query.kind, QueryKind::Enum);
190 }
191
192 #[test]
193 fn test_auto_detect_yaml() {
194 let yaml = "kind: Trait\n";
195 let query = QueryParser::parse(yaml).unwrap();
196 assert_eq!(query.kind, QueryKind::Trait);
197 }
198
199 #[test]
200 fn test_from_cli_exact() {
201 let query = QueryParser::from_cli("fn", "process").unwrap();
202 assert_eq!(query.kind, QueryKind::Function);
203 let m = query.r#match.unwrap();
204 assert!(matches!(m.name, Some(NameMatcher::Exact(ref s)) if s == "process"));
205 }
206
207 #[test]
208 fn test_from_cli_glob() {
209 let query = QueryParser::from_cli("struct", "*Config").unwrap();
210 assert_eq!(query.kind, QueryKind::Struct);
211 let m = query.r#match.unwrap();
212 assert!(matches!(m.name, Some(NameMatcher::Detailed(_))));
213 }
214
215 #[test]
216 fn test_validate_or_query() {
217 let yaml = r#"
218kind: Or
219queries: []
220"#;
221 let result = QueryParser::from_yaml(yaml);
222 assert!(result.is_err());
223 }
224
225 #[test]
226 fn test_validate_pattern_query() {
227 let yaml = r#"
228kind: Pattern
229"#;
230 let result = QueryParser::from_yaml(yaml);
231 assert!(result.is_err());
232 }
233
234 #[test]
235 fn test_parse_view_mode() {
236 assert_eq!(parse_view_mode("snippet"), Some(ViewMode::Snippet));
237 assert_eq!(parse_view_mode("PRECISE"), Some(ViewMode::Precise));
238 assert_eq!(parse_view_mode("count"), Some(ViewMode::Count));
239 assert_eq!(parse_view_mode("def"), Some(ViewMode::Def));
240 assert_eq!(parse_view_mode("DEF"), Some(ViewMode::Def));
241 assert_eq!(parse_view_mode("invalid"), None);
242 }
243}