use crate::schema::{MatchAttrs, NameMatcher, Query, QueryKind, ViewMode};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("YAML parse error: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("Invalid query: {0}")]
Invalid(String),
#[error("Unknown kind: {0}")]
UnknownKind(String),
}
pub struct QueryParser;
impl QueryParser {
pub fn from_yaml(yaml: &str) -> Result<Query, ParseError> {
let query: Query = serde_yaml::from_str(yaml)?;
Self::validate(&query)?;
Ok(query)
}
pub fn from_json(json: &str) -> Result<Query, ParseError> {
let query: Query = serde_json::from_str(json)?;
Self::validate(&query)?;
Ok(query)
}
pub fn parse(input: &str) -> Result<Query, ParseError> {
let trimmed = input.trim();
if trimmed.starts_with('{') {
return Self::from_json(input);
}
Self::from_yaml(input)
}
pub fn from_cli(kind_str: &str, pattern: &str) -> Result<Query, ParseError> {
let kind = Self::parse_kind(kind_str)?;
let name_matcher = Self::parse_name_pattern(pattern);
Ok(Query {
kind,
r#match: Some(MatchAttrs {
name: Some(name_matcher),
..Default::default()
}),
inner: vec![],
queries: vec![],
name: None,
resolve: None,
scope: None,
view: None,
limit: None,
body: None,
relations: None,
})
}
fn parse_kind(s: &str) -> Result<QueryKind, ParseError> {
match s.to_lowercase().as_str() {
"any" | "*" => Ok(QueryKind::Any),
"fn" | "function" => Ok(QueryKind::Function),
"struct" => Ok(QueryKind::Struct),
"enum" => Ok(QueryKind::Enum),
"trait" => Ok(QueryKind::Trait),
"impl" => Ok(QueryKind::Impl),
"mod" | "module" => Ok(QueryKind::Mod),
"const" => Ok(QueryKind::Const),
"static" => Ok(QueryKind::Static),
"type" | "typealias" => Ok(QueryKind::TypeAlias),
_ => Err(ParseError::UnknownKind(s.to_string())),
}
}
fn parse_name_pattern(pattern: &str) -> NameMatcher {
if pattern.contains('*') || pattern.contains('?') {
NameMatcher::Detailed(crate::schema::NameMatcherDetailed {
glob: Some(pattern.to_string()),
contains: None,
starts_with: None,
ends_with: None,
regex: None,
ignore_case: None,
ignore_word_separate: None,
})
} else {
NameMatcher::Exact(pattern.to_string())
}
}
fn validate(query: &Query) -> Result<(), ParseError> {
match query.kind {
QueryKind::Or | QueryKind::And => {
if query.queries.is_empty() {
return Err(ParseError::Invalid(
"Or/And query requires 'queries' field".to_string(),
));
}
for q in &query.queries {
Self::validate(q)?;
}
}
QueryKind::Pattern => {
if query.name.is_none() {
return Err(ParseError::Invalid(
"Pattern query requires 'name' field".to_string(),
));
}
}
_ => {
for q in &query.inner {
Self::validate(q)?;
}
}
}
Ok(())
}
}
pub fn parse_view_mode(s: &str) -> Option<ViewMode> {
match s.to_lowercase().as_str() {
"snippet" => Some(ViewMode::Snippet),
"precise" => Some(ViewMode::Precise),
"count" => Some(ViewMode::Count),
"def" => Some(ViewMode::Def),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_yaml() {
let yaml = r#"
kind: Function
match:
name: "process"
"#;
let query = QueryParser::from_yaml(yaml).unwrap();
assert_eq!(query.kind, QueryKind::Function);
}
#[test]
fn test_parse_json() {
let json = r#"{"kind": "Struct", "match": {"name": "Config"}}"#;
let query = QueryParser::from_json(json).unwrap();
assert_eq!(query.kind, QueryKind::Struct);
}
#[test]
fn test_auto_detect_json() {
let json = r#"{"kind": "Enum"}"#;
let query = QueryParser::parse(json).unwrap();
assert_eq!(query.kind, QueryKind::Enum);
}
#[test]
fn test_auto_detect_yaml() {
let yaml = "kind: Trait\n";
let query = QueryParser::parse(yaml).unwrap();
assert_eq!(query.kind, QueryKind::Trait);
}
#[test]
fn test_from_cli_exact() {
let query = QueryParser::from_cli("fn", "process").unwrap();
assert_eq!(query.kind, QueryKind::Function);
let m = query.r#match.unwrap();
assert!(matches!(m.name, Some(NameMatcher::Exact(ref s)) if s == "process"));
}
#[test]
fn test_from_cli_glob() {
let query = QueryParser::from_cli("struct", "*Config").unwrap();
assert_eq!(query.kind, QueryKind::Struct);
let m = query.r#match.unwrap();
assert!(matches!(m.name, Some(NameMatcher::Detailed(_))));
}
#[test]
fn test_validate_or_query() {
let yaml = r#"
kind: Or
queries: []
"#;
let result = QueryParser::from_yaml(yaml);
assert!(result.is_err());
}
#[test]
fn test_validate_pattern_query() {
let yaml = r#"
kind: Pattern
"#;
let result = QueryParser::from_yaml(yaml);
assert!(result.is_err());
}
#[test]
fn test_parse_view_mode() {
assert_eq!(parse_view_mode("snippet"), Some(ViewMode::Snippet));
assert_eq!(parse_view_mode("PRECISE"), Some(ViewMode::Precise));
assert_eq!(parse_view_mode("count"), Some(ViewMode::Count));
assert_eq!(parse_view_mode("def"), Some(ViewMode::Def));
assert_eq!(parse_view_mode("DEF"), Some(ViewMode::Def));
assert_eq!(parse_view_mode("invalid"), None);
}
}