use std::sync::Arc;
use super::{BuildError, ConditionBuilder, RegexBuilder};
use crate::query::registry::FieldRegistry;
use crate::query::types::{
Expr, FieldType, Operator, Query as QueryAST, RegexFlags, RegexValue, Span, Value,
};
#[derive(Clone, Debug)]
#[must_use = "QueryBuilder does nothing until .build() is called"]
pub struct QueryBuilder {
expr: BuilderExpr,
errors: Vec<BuildError>,
}
#[derive(Clone, Debug)]
enum BuilderExpr {
Condition(ConditionBuilder),
And(Vec<QueryBuilder>),
Or(Vec<QueryBuilder>),
Not(Box<QueryBuilder>),
Empty,
}
impl QueryBuilder {
pub fn new() -> Self {
Self {
expr: BuilderExpr::Empty,
errors: Vec::new(),
}
}
pub fn kind(value: impl Into<String>) -> Self {
Self::condition("kind", Operator::Equal, Value::String(value.into()))
}
pub fn kind_any(values: &[&str]) -> Self {
Self::any(values.iter().map(|v| Self::kind(*v)).collect())
}
pub fn name(value: impl Into<String>) -> Self {
Self::condition("name", Operator::Equal, Value::String(value.into()))
}
pub fn name_matches(pattern: impl Into<String>) -> Self {
let regex = RegexValue {
pattern: pattern.into(),
flags: RegexFlags::default(),
};
Self::condition("name", Operator::Regex, Value::Regex(regex))
}
pub fn name_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
where
F: FnOnce(RegexBuilder) -> RegexBuilder,
{
let builder = RegexBuilder::new(pattern);
let configured = configure(builder);
Self::condition(
"name",
Operator::Regex,
Value::Regex(configured.into_regex_value()),
)
}
pub fn lang(value: impl Into<String>) -> Self {
Self::condition("lang", Operator::Equal, Value::String(value.into()))
}
pub fn language(value: impl Into<String>) -> Self {
Self::lang(value)
}
pub fn path(value: impl Into<String>) -> Self {
Self::condition("path", Operator::Equal, Value::String(value.into()))
}
pub fn file(value: impl Into<String>) -> Self {
Self::path(value)
}
pub fn path_matches(pattern: impl Into<String>) -> Self {
let regex = RegexValue {
pattern: pattern.into(),
flags: RegexFlags::default(),
};
Self::condition("path", Operator::Regex, Value::Regex(regex))
}
pub fn path_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
where
F: FnOnce(RegexBuilder) -> RegexBuilder,
{
let builder = RegexBuilder::new(pattern);
let configured = configure(builder);
Self::condition(
"path",
Operator::Regex,
Value::Regex(configured.into_regex_value()),
)
}
pub fn repo(value: impl Into<String>) -> Self {
Self::condition("repo", Operator::Equal, Value::String(value.into()))
}
pub fn parent(value: impl Into<String>) -> Self {
Self::condition("parent", Operator::Equal, Value::String(value.into()))
}
pub fn text_matches(pattern: impl Into<String>) -> Self {
let regex = RegexValue {
pattern: pattern.into(),
flags: RegexFlags::default(),
};
Self::condition("text", Operator::Regex, Value::Regex(regex))
}
pub fn text_matches_with<F>(pattern: impl Into<String>, configure: F) -> Self
where
F: FnOnce(RegexBuilder) -> RegexBuilder,
{
let builder = RegexBuilder::new(pattern);
let configured = configure(builder);
Self::condition(
"text",
Operator::Regex,
Value::Regex(configured.into_regex_value()),
)
}
pub fn callers(symbol: impl Into<String>) -> Self {
Self::condition("callers", Operator::Equal, Value::String(symbol.into()))
}
pub fn callees(symbol: impl Into<String>) -> Self {
Self::condition("callees", Operator::Equal, Value::String(symbol.into()))
}
pub fn imports(module: impl Into<String>) -> Self {
Self::condition("imports", Operator::Equal, Value::String(module.into()))
}
pub fn exports(value: impl Into<String>) -> Self {
Self::condition("exports", Operator::Equal, Value::String(value.into()))
}
pub fn returns(type_name: impl Into<String>) -> Self {
Self::condition("returns", Operator::Equal, Value::String(type_name.into()))
}
pub fn references(symbol: impl Into<String>) -> Self {
Self::condition("references", Operator::Equal, Value::String(symbol.into()))
}
pub fn scope(value: impl Into<String>) -> Self {
Self::condition("scope", Operator::Equal, Value::String(value.into()))
}
pub fn scope_type(value: impl Into<String>) -> Self {
Self::condition("scope.type", Operator::Equal, Value::String(value.into()))
}
pub fn scope_name(value: impl Into<String>) -> Self {
Self::condition("scope.name", Operator::Equal, Value::String(value.into()))
}
pub fn scope_parent(value: impl Into<String>) -> Self {
Self::condition("scope.parent", Operator::Equal, Value::String(value.into()))
}
pub fn scope_ancestor(value: impl Into<String>) -> Self {
Self::condition(
"scope.ancestor",
Operator::Equal,
Value::String(value.into()),
)
}
pub fn field(name: impl Into<String>, value: impl Into<Value>) -> Self {
Self::condition_value(name.into(), Operator::Equal, value.into())
}
pub fn field_matches(name: impl Into<String>, pattern: impl Into<String>) -> Self {
let regex = RegexValue {
pattern: pattern.into(),
flags: RegexFlags::default(),
};
Self::condition_value(name.into(), Operator::Regex, Value::Regex(regex))
}
pub fn field_matches_with<F>(
name: impl Into<String>,
pattern: impl Into<String>,
configure: F,
) -> Self
where
F: FnOnce(RegexBuilder) -> RegexBuilder,
{
let builder = RegexBuilder::new(pattern);
let configured = configure(builder);
Self::condition_value(
name.into(),
Operator::Regex,
Value::Regex(configured.into_regex_value()),
)
}
pub fn field_gt(name: impl Into<String>, value: i64) -> Self {
Self::condition_value(name.into(), Operator::Greater, Value::Number(value))
}
pub fn field_gte(name: impl Into<String>, value: i64) -> Self {
Self::condition_value(name.into(), Operator::GreaterEq, Value::Number(value))
}
pub fn field_lt(name: impl Into<String>, value: i64) -> Self {
Self::condition_value(name.into(), Operator::Less, Value::Number(value))
}
pub fn field_lte(name: impl Into<String>, value: i64) -> Self {
Self::condition_value(name.into(), Operator::LessEq, Value::Number(value))
}
fn condition(field: &'static str, operator: Operator, value: Value) -> Self {
Self {
expr: BuilderExpr::Condition(ConditionBuilder::new_static(field, operator, value)),
errors: Vec::new(),
}
}
fn condition_value(field: String, operator: Operator, value: Value) -> Self {
Self {
expr: BuilderExpr::Condition(ConditionBuilder::new(field, operator, value)),
errors: Vec::new(),
}
}
}
impl QueryBuilder {
pub fn all(conditions: Vec<QueryBuilder>) -> Self {
let errors = conditions.iter().flat_map(|c| c.errors.clone()).collect();
Self {
expr: BuilderExpr::And(conditions),
errors,
}
}
pub fn any(conditions: Vec<QueryBuilder>) -> Self {
let errors = conditions.iter().flat_map(|c| c.errors.clone()).collect();
Self {
expr: BuilderExpr::Or(conditions),
errors,
}
}
pub fn and(self, other: QueryBuilder) -> Self {
let mut errors = self.errors;
errors.extend(other.errors.clone());
match self.expr {
BuilderExpr::Empty => Self {
expr: other.expr,
errors,
},
BuilderExpr::And(mut exprs) => {
exprs.push(other);
Self {
expr: BuilderExpr::And(exprs),
errors,
}
}
_ => Self {
expr: BuilderExpr::And(vec![
Self {
expr: self.expr,
errors: Vec::new(),
},
other,
]),
errors,
},
}
}
pub fn or(self, other: QueryBuilder) -> Self {
let mut errors = self.errors;
errors.extend(other.errors.clone());
match self.expr {
BuilderExpr::Empty => Self {
expr: other.expr,
errors,
},
BuilderExpr::Or(mut exprs) => {
exprs.push(other);
Self {
expr: BuilderExpr::Or(exprs),
errors,
}
}
_ => Self {
expr: BuilderExpr::Or(vec![
Self {
expr: self.expr,
errors: Vec::new(),
},
other,
]),
errors,
},
}
}
pub fn and_not(self, other: QueryBuilder) -> Self {
self.and(Self::negate(other))
}
pub fn negate(builder: QueryBuilder) -> Self {
let errors = builder.errors.clone();
Self {
expr: BuilderExpr::Not(Box::new(builder)),
errors,
}
}
}
impl QueryBuilder {
pub fn build(self) -> Result<Arc<QueryAST>, BuildError> {
let registry = FieldRegistry::with_core_fields();
self.build_with_registry(®istry)
}
pub fn build_with_registry(
self,
registry: &FieldRegistry,
) -> Result<Arc<QueryAST>, BuildError> {
if !self.errors.is_empty() {
return Err(BuildError::Multiple(self.errors));
}
let expr = self.into_expr(registry)?;
Ok(Arc::new(QueryAST {
root: expr,
span: Span::synthetic(),
}))
}
fn into_expr(self, registry: &FieldRegistry) -> Result<Expr, BuildError> {
match self.expr {
BuilderExpr::Empty => Err(BuildError::EmptyQuery),
BuilderExpr::Condition(ref cond) => {
Self::validate_condition(cond, registry)?;
Ok(Expr::Condition(cond.clone().into_condition(registry)))
}
BuilderExpr::And(exprs) => {
let children: Result<Vec<_>, _> =
exprs.into_iter().map(|e| e.into_expr(registry)).collect();
Ok(Expr::And(children?))
}
BuilderExpr::Or(exprs) => {
let children: Result<Vec<_>, _> =
exprs.into_iter().map(|e| e.into_expr(registry)).collect();
Ok(Expr::Or(children?))
}
BuilderExpr::Not(inner) => Ok(Expr::Not(Box::new(inner.into_expr(registry)?))),
}
}
fn validate_condition(
cond: &ConditionBuilder,
registry: &FieldRegistry,
) -> Result<(), BuildError> {
let descriptor = registry
.get(cond.field())
.ok_or_else(|| BuildError::UnknownField {
field: cond.field().to_string(),
available: registry.field_names().join(", "),
})?;
if !descriptor.supports_operator(cond.operator()) {
return Err(BuildError::InvalidOperator {
field: cond.field().to_string(),
operator: cond.operator().clone(),
field_type: format!("{:?}", descriptor.field_type),
});
}
Self::validate_value_type(cond.field(), &descriptor.field_type, cond.value())?;
Self::validate_regex_pattern(cond.value())?;
Self::validate_enum_value(cond.field(), cond.value(), &descriptor.field_type)?;
Ok(())
}
fn validate_regex_pattern(value: &Value) -> Result<(), BuildError> {
if let Value::Regex(regex_value) = value {
let has_lookaround = regex_value.pattern.contains("(?=")
|| regex_value.pattern.contains("(?!")
|| regex_value.pattern.contains("(?<=")
|| regex_value.pattern.contains("(?<!");
if has_lookaround {
fancy_regex::Regex::new(®ex_value.pattern).map_err(|e| {
BuildError::InvalidFancyRegex {
pattern: regex_value.pattern.clone(),
error: e.to_string(),
}
})?;
} else {
let mut builder = regex::RegexBuilder::new(®ex_value.pattern);
builder.case_insensitive(regex_value.flags.case_insensitive);
builder.multi_line(regex_value.flags.multiline);
builder.dot_matches_new_line(regex_value.flags.dot_all);
builder.build()?;
}
}
Ok(())
}
fn validate_enum_value(
field: &str,
value: &Value,
field_type: &FieldType,
) -> Result<(), BuildError> {
if let (FieldType::Enum(valid), Value::String(s)) = (field_type, value)
&& !valid.contains(&s.as_str())
{
return Err(BuildError::InvalidEnumValue {
field: field.to_string(),
value: s.clone(),
valid: valid.join(", "),
});
}
Ok(())
}
fn validate_value_type(
field: &str,
field_type: &FieldType,
value: &Value,
) -> Result<(), BuildError> {
let is_valid = matches!(
(field_type, value),
(
FieldType::String | FieldType::Path | FieldType::Enum(_),
Value::String(_) | Value::Regex(_)
) | (FieldType::Number, Value::Number(_))
| (FieldType::Bool, Value::Boolean(_)) );
if !is_valid {
return Err(BuildError::ValueTypeMismatch {
field: field.to_string(),
expected: format!("{field_type:?}"),
actual: value.type_name().to_string(),
});
}
Ok(())
}
}
impl Default for QueryBuilder {
fn default() -> Self {
Self::new()
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(s.to_string())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<i64> for Value {
fn from(n: i64) -> Self {
Value::Number(n)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Boolean(b)
}
}