Crates.io | search-query-parser |
lib.rs | search-query-parser |
version | 0.1.4 |
source | src |
created_at | 2022-09-20 02:16:48.399414 |
updated_at | 2023-07-14 16:01:22.193137 |
description | parse complex search query into layered search conditions, so it will be easy to construct Elasticsearch query DSL or something else. |
homepage | https://github.com/dimmy82/search-query-parser |
repository | https://github.com/dimmy82/search-query-parser |
max_upload_size | |
id | 669528 |
size | 140,358 |
search-query-parser is made to parse complex search query into layered search conditions, so it will be easy to construct Elasticsearch query DSL or something else.
the complex search query like this: ↓↓↓
(word1 and -word2) or (("phrase word 1" or -"phrase word 2") and -(" a long phrase word " or word3))
will be parsed into layered search conditions like this: ↓↓↓
Condition::Operator(
Operator::Or,
vec![
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("word1".into()),
Condition::Not(Box::new(Condition::Keyword("word2".into()))),
]
),
Condition::Operator(
Operator::And,
vec![
Condition::Operator(
Operator::Or,
vec![
Condition::PhraseKeyword("phrase word 1".into()),
Condition::Not(Box::new(Condition::PhraseKeyword(
"phrase word 2".into()
)))
]
),
Condition::Not(Box::new(Condition::Operator(
Operator::Or,
vec![
Condition::PhraseKeyword(" a long phrase word ".into()),
Condition::Keyword("word3".into())
]
)))
]
),
]
)
the conditions are constructed by the enum Condition
and enum Operator
.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Condition {
None,
Keyword(String),
PhraseKeyword(String),
Not(Box<Condition>),
Operator(Operator, Vec<Condition>),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Operator {
And,
Or,
}
[dependencies]
search-query-parser = "0.1.4"
use search_query_parser::parse_query_to_condition;
let condition = parse_query_to_condition("any query string you like")?;
refer to search-query-parser-api repository
refer to search-query-parser-cdylib repository
AND
operatorfn test_keywords_concat_with_spaces() {
let actual = parse_query_to_condition("word1 word2").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("word1".into()),
Condition::Keyword("word2".into())
]
)
)
}
AND
operator has higher priority than OR
operatorfn test_keywords_concat_with_and_or() {
let actual =
parse_query_to_condition("word1 OR word2 AND word3").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("word1".into()),
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("word2".into()),
Condition::Keyword("word3".into()),
]
)
]
)
)
}
fn test_brackets() {
let actual =
parse_query_to_condition("word1 AND (word2 OR word3)")
.unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("word1".into()),
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("word2".into()),
Condition::Keyword("word3".into()),
]
)
]
)
)
}
fn test_double_quote() {
let actual = parse_query_to_condition(
"\"word1 AND (word2 OR word3)\" word4",
)
.unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::PhraseKeyword(
"word1 AND (word2 OR word3)".into()
),
Condition::Keyword("word4".into()),
]
)
)
}
※ it can be used before keyword, phrase keyword or brackets
fn test_minus() {
let actual = parse_query_to_condition(
"-word1 -\"word2\" -(word3 OR word4)",
)
.unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Not(Box::new(Condition::Keyword("word1".into()))),
Condition::Not(Box::new(Condition::PhraseKeyword("word2".into()))),
Condition::Not(Box::new(Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("word3".into()),
Condition::Keyword("word4".into())
]
))),
]
)
)
}
fn test_empty_brackets() {
let actual = parse_query_to_condition("A AND () AND B").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("A".into()),
Condition::Keyword("B".into()),
]
)
)
}
fn test_reverse_brackets() {
let actual = parse_query_to_condition("A OR B) AND (C OR D").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("A".into()),
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("B".into()),
Condition::Keyword("C".into()),
]
),
Condition::Keyword("D".into()),
]
)
)
}
fn test_missing_brackets() {
let actual = parse_query_to_condition("(A OR B) AND (C").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("A".into()),
Condition::Keyword("B".into()),
]
),
Condition::Keyword("C".into()),
]
)
)
}
fn test_empty_phrase_keywords() {
let actual = parse_query_to_condition("A AND \"\" AND B").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Keyword("A".into()),
Condition::Keyword("B".into()),
]
)
)
}
fn test_invalid_double_quote() {
let actual = parse_query_to_condition("\"A\" OR \"B OR C").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::Or,
vec![
Condition::PhraseKeyword("A".into()),
Condition::Keyword("B".into()),
Condition::Keyword("C".into()),
]
)
)
}
fn test_invalid_and_or() {
let actual = parse_query_to_condition("A AND OR B").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("A".into()),
Condition::Keyword("B".into()),
]
)
)
}
fn test_unnecessary_nest_brackets() {
let actual = parse_query_to_condition("(A OR (B OR C)) AND D").unwrap();
assert_eq!(
actual,
Condition::Operator(
Operator::And,
vec![
Condition::Operator(
Operator::Or,
vec![
Condition::Keyword("A".into()),
Condition::Keyword("B".into()),
Condition::Keyword("C".into()),
]
),
Condition::Keyword("D".into()),
]
)
)
}