Skip to main content

ryo_query_language/
parser.rs

1//! RyoQL Parser - YAML/JSON/CLI パース
2
3use crate::schema::{MatchAttrs, NameMatcher, Query, QueryKind, ViewMode};
4use thiserror::Error;
5
6/// パースエラー
7#[derive(Debug, Error)]
8pub enum ParseError {
9    /// YAML 構文エラー。
10    #[error("YAML parse error: {0}")]
11    Yaml(#[from] serde_yaml::Error),
12
13    /// JSON 構文エラー。
14    #[error("JSON parse error: {0}")]
15    Json(#[from] serde_json::Error),
16
17    /// 構造的に invalid な query (validate 失敗)。
18    #[error("Invalid query: {0}")]
19    Invalid(String),
20
21    /// 未知の `QueryKind` 文字列。
22    #[error("Unknown kind: {0}")]
23    UnknownKind(String),
24}
25
26/// クエリパーサー
27pub struct QueryParser;
28
29impl QueryParser {
30    /// YAMLからパース
31    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    /// JSONからパース
38    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    /// 自動判定してパース (YAMLを優先)
45    pub fn parse(input: &str) -> Result<Query, ParseError> {
46        // JSONっぽければJSONとして試す
47        let trimmed = input.trim();
48        if trimmed.starts_with('{') {
49            return Self::from_json(input);
50        }
51        Self::from_yaml(input)
52    }
53
54    /// CLI簡易形式からパース
55    ///
56    /// 例:
57    /// - `fn process` → kind: Function, name: "process"
58    /// - `struct *Config` → kind: Struct, name: { glob: "*Config" }
59    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    /// kind文字列をパース
82    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    /// 名前パターンをパース
99    ///
100    /// - `*Config` → glob
101    /// - `process*` → glob
102    /// - `*process*` → contains
103    /// - `process` → exact
104    fn parse_name_pattern(pattern: &str) -> NameMatcher {
105        if pattern.contains('*') || pattern.contains('?') {
106            // glob pattern
107            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    /// クエリを検証
122    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                // inner検証
143                for q in &query.inner {
144                    Self::validate(q)?;
145                }
146            }
147        }
148        Ok(())
149    }
150}
151
152/// ViewMode文字列パース
153pub 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}