caio.co/de/cantine

Add support for strict field names

Id
5b501fcd44de096ce03ba91a8312624d4333f39b
Author
Caio
Commit time
2020-02-29T22:48:13+01:00

Modified tique/src/queryparser/parser.rs

@@ -1,24 +1,26
use nom::{
self,
branch::alt,
bytes::complete::take_while1,
character::complete::{char as is_char, multispace0},
- combinator::map,
+ combinator::{map, map_res},
multi::many0,
sequence::{delimited, preceded, separated_pair},
IResult,
};

#[derive(Debug, PartialEq)]
-pub(crate) struct RawQuery<'a> {
+pub struct RawQuery<'a> {
pub input: &'a str,
pub is_negated: bool,
pub is_phrase: bool,
pub field_name: Option<&'a str>,
}

+pub const FIELD_SEP: char = ':';
+
impl<'a> RawQuery<'a> {
- fn new(input: &'a str) -> Self {
+ pub fn new(input: &'a str) -> Self {
Self {
input,
is_negated: false,
@@ -27,48 +29,96
}
}

- fn negated(mut self) -> Self {
+ pub fn negated(mut self) -> Self {
debug_assert!(!self.is_negated);
self.is_negated = true;
self
}

- fn phrase(mut self) -> Self {
+ pub fn phrase(mut self) -> Self {
debug_assert!(!self.is_phrase);
self.is_phrase = true;
self
}

- fn with_field(mut self, name: &'a str) -> Self {
+ pub fn with_field(mut self, name: &'a str) -> Self {
debug_assert_eq!(None, self.field_name);
self.field_name = Some(name);
self
}
}

-pub(crate) fn parse_query(input: &str) -> IResult<&str, Vec<RawQuery>> {
+pub trait FieldNameValidator {
+ fn check(&self, field_name: &str) -> bool;
+}
+
+impl<T> FieldNameValidator for Vec<T>
+where
+ T: for<'a> PartialEq<&'a str>,
+{
+ fn check(&self, field_name: &str) -> bool {
+ self.iter().any(|item| item == &field_name)
+ }
+}
+
+impl FieldNameValidator for bool {
+ fn check(&self, _field_name: &str) -> bool {
+ *self
+ }
+}
+
+pub fn parse_query(input: &str) -> IResult<&str, Vec<RawQuery>> {
+ parse_query_with_fields(input, &false)
+}
+
+pub fn parse_query_with_fields<'a, C: FieldNameValidator>(
+ input: &'a str,
+ validator: &'a C,
+) -> IResult<&'a str, Vec<RawQuery<'a>>> {
many0(delimited(
multispace0,
- alt((negated_query, field_prefixed_query, any_field_query)),
+ alt((
+ |input| negated_query(input, validator),
+ |input| field_prefixed_query(input, validator),
+ any_field_query,
+ )),
multispace0,
))(input)
}

-fn negated_query(input: &str) -> IResult<&str, RawQuery> {
+fn negated_query<'a, C: FieldNameValidator>(
+ input: &'a str,
+ validator: &'a C,
+) -> IResult<&'a str, RawQuery<'a>> {
map(
- preceded(is_char('-'), alt((field_prefixed_query, any_field_query))),
+ preceded(
+ is_char('-'),
+ alt((
+ |input| field_prefixed_query(input, validator),
+ any_field_query,
+ )),
+ ),
|query| query.negated(),
)(input)
}

-fn field_prefixed_query(input: &str) -> IResult<&str, RawQuery> {
- map(
+fn field_prefixed_query<'a, C: FieldNameValidator>(
+ input: &'a str,
+ validator: &'a C,
+) -> IResult<&'a str, RawQuery<'a>> {
+ map_res(
separated_pair(
- take_while1(|c| c != ':' && is_term_char(c)),
- is_char(':'),
+ take_while1(|c| c != FIELD_SEP && is_term_char(c)),
+ is_char(FIELD_SEP),
any_field_query,
),
- |(name, term)| term.with_field(name),
+ |(name, term)| {
+ if validator.check(name) {
+ Ok(term.with_field(name))
+ } else {
+ Err("Invalid field")
+ }
+ },
)(input)
}

@@ -143,9 +193,51
}

#[test]
+ fn check_field_behavior() {
+ let input = "title:banana ingredient:sugar";
+
+ // No field support: fields end up in the term
+ assert_eq!(
+ parse_query_with_fields(input, &false),
+ Ok((
+ "",
+ vec![
+ RawQuery::new("title:banana"),
+ RawQuery::new("ingredient:sugar"),
+ ]
+ ))
+ );
+
+ // Any field support: field names are not valitdated at all
+ assert_eq!(
+ parse_query_with_fields(input, &true),
+ Ok((
+ "",
+ vec![
+ RawQuery::new("banana").with_field("title"),
+ RawQuery::new("sugar").with_field("ingredient"),
+ ]
+ ))
+ );
+
+ // Strict field support: known fields are identified, unknown
+ // ones are part of the term
+ assert_eq!(
+ parse_query_with_fields(input, &vec!["ingredient"]),
+ Ok((
+ "",
+ vec![
+ RawQuery::new("title:banana"),
+ RawQuery::new("sugar").with_field("ingredient"),
+ ]
+ ))
+ );
+ }
+
+ #[test]
fn garbage_handling() {
assert_eq!(
- parse_query("- -field: -\"\" body:\"\""),
+ parse_query_with_fields("- -field: -\"\" body:\"\"", &true),
Ok((
"",
vec![
@@ -161,7 +253,7
#[test]
fn parse_term_with_field() {
assert_eq!(
- parse_query("title:potato:queen -instructions:mash -body:\"how to fail\" ingredient:\"golden peeler\""),
+ parse_query_with_fields("title:potato:queen -instructions:mash -body:\"how to fail\" ingredient:\"golden peeler\"", &true),
Ok((
"",
vec![