2021-10-22 07:59:38 +08:00
|
|
|
|
use nom::branch::alt;
|
2021-11-04 21:22:35 +08:00
|
|
|
|
use nom::bytes::complete::{take_till, take_while, take_while1};
|
|
|
|
|
use nom::character::complete::{char, multispace0};
|
|
|
|
|
use nom::combinator::cut;
|
|
|
|
|
use nom::sequence::{delimited, terminated};
|
2021-10-22 07:59:38 +08:00
|
|
|
|
|
2021-11-09 07:50:15 +08:00
|
|
|
|
use crate::error::NomErrorExt;
|
2021-11-04 21:22:35 +08:00
|
|
|
|
use crate::{parse_geo_point, parse_geo_radius, Error, ErrorKind, IResult, Span, Token};
|
2021-10-22 07:59:38 +08:00
|
|
|
|
|
|
|
|
|
/// value = WS* ~ ( word | singleQuoted | doubleQuoted) ~ WS*
|
2021-11-03 03:27:07 +08:00
|
|
|
|
pub fn parse_value(input: Span) -> IResult<Token> {
|
2021-11-08 22:30:26 +08:00
|
|
|
|
// to get better diagnostic message we are going to strip the left whitespaces from the input right now
|
|
|
|
|
let (input, _) = take_while(char::is_whitespace)(input)?;
|
|
|
|
|
|
|
|
|
|
// then, we want to check if the user is misusing a geo expression
|
2021-11-09 07:57:46 +08:00
|
|
|
|
// This expression can’t finish without error.
|
|
|
|
|
// We want to return an error in case of failure.
|
|
|
|
|
if let Err(err) = parse_geo_point(input) {
|
|
|
|
|
if err.is_failure() {
|
|
|
|
|
return Err(err);
|
|
|
|
|
}
|
2021-11-04 21:22:35 +08:00
|
|
|
|
}
|
|
|
|
|
match parse_geo_radius(input) {
|
2021-11-04 23:20:53 +08:00
|
|
|
|
Ok(_) => return Err(nom::Err::Failure(Error::new_from_kind(input, ErrorKind::MisusedGeo))),
|
2021-11-04 21:22:35 +08:00
|
|
|
|
// if we encountered a failure it means the user badly wrote a _geoRadius filter.
|
|
|
|
|
// But instead of showing him how to fix his syntax we are going to tell him he should not use this filter as a value.
|
|
|
|
|
Err(e) if e.is_failure() => {
|
2021-11-04 23:20:53 +08:00
|
|
|
|
return Err(nom::Err::Failure(Error::new_from_kind(input, ErrorKind::MisusedGeo)))
|
2021-11-04 21:22:35 +08:00
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 07:59:38 +08:00
|
|
|
|
// singleQuoted = "'" .* all but quotes "'"
|
2021-11-09 07:45:46 +08:00
|
|
|
|
let simple_quoted = take_till(|c: char| c == '\'');
|
2021-10-22 07:59:38 +08:00
|
|
|
|
// doubleQuoted = "\"" (word | spaces)* "\""
|
2021-11-09 07:45:46 +08:00
|
|
|
|
let double_quoted = take_till(|c: char| c == '"');
|
2021-10-22 07:59:38 +08:00
|
|
|
|
// word = (alphanumeric | _ | - | .)+
|
2021-11-09 07:58:23 +08:00
|
|
|
|
let word = take_while1(is_value_component);
|
2021-10-22 07:59:38 +08:00
|
|
|
|
|
2021-11-08 22:30:26 +08:00
|
|
|
|
// this parser is only used when an error is encountered and it parse the
|
|
|
|
|
// largest string possible that do not contain any “language” syntax.
|
|
|
|
|
// If we try to parse `name = 🦀 AND language = rust` we want to return an
|
|
|
|
|
// error saying we could not parse `🦀`. Not that no value were found or that
|
|
|
|
|
// we could note parse `🦀 AND language = rust`.
|
2021-11-04 21:22:35 +08:00
|
|
|
|
// we want to remove the space before entering the alt because if we don't,
|
|
|
|
|
// when we create the errors from the output of the alt we have spaces everywhere
|
2021-11-08 22:30:26 +08:00
|
|
|
|
let error_word = take_till::<_, _, Error>(is_syntax_component);
|
2021-11-04 21:22:35 +08:00
|
|
|
|
|
|
|
|
|
terminated(
|
|
|
|
|
alt((
|
2021-11-08 22:30:26 +08:00
|
|
|
|
delimited(char('\''), cut(simple_quoted), cut(char('\''))),
|
|
|
|
|
delimited(char('"'), cut(double_quoted), cut(char('"'))),
|
2021-11-04 21:22:35 +08:00
|
|
|
|
word,
|
|
|
|
|
)),
|
|
|
|
|
multispace0,
|
|
|
|
|
)(input)
|
2021-10-22 07:59:38 +08:00
|
|
|
|
.map(|(s, t)| (s, t.into()))
|
2021-11-08 22:30:26 +08:00
|
|
|
|
// if we found nothing in the alt it means the user specified something that was not recognized as a value
|
|
|
|
|
.map_err(|e: nom::Err<Error>| {
|
|
|
|
|
e.map_err(|_| Error::new_from_kind(error_word(input).unwrap().1, ErrorKind::ExpectedValue))
|
|
|
|
|
})
|
2021-11-04 21:22:35 +08:00
|
|
|
|
// if we found encountered a failure it means the user really tried to input a value, but had an unmatched quote
|
2021-11-04 23:20:53 +08:00
|
|
|
|
.map_err(|e| {
|
|
|
|
|
e.map_fail(|c| Error::new_from_kind(input, ErrorKind::MissingClosingDelimiter(c.char())))
|
|
|
|
|
})
|
2021-10-22 07:59:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-09 07:58:23 +08:00
|
|
|
|
fn is_value_component(c: char) -> bool {
|
2021-10-22 07:59:38 +08:00
|
|
|
|
c.is_alphanumeric() || ['_', '-', '.'].contains(&c)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 22:30:26 +08:00
|
|
|
|
fn is_syntax_component(c: char) -> bool {
|
2021-11-09 23:47:54 +08:00
|
|
|
|
c.is_whitespace() || ['(', ')', '=', '<', '>'].contains(&c)
|
2021-11-08 22:30:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 07:59:38 +08:00
|
|
|
|
#[cfg(test)]
|
2021-11-08 22:30:26 +08:00
|
|
|
|
pub mod test {
|
|
|
|
|
use nom::Finish;
|
|
|
|
|
|
2021-10-22 07:59:38 +08:00
|
|
|
|
use super::*;
|
|
|
|
|
use crate::tests::rtok;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn name() {
|
|
|
|
|
let test_case = [
|
|
|
|
|
("channel", rtok("", "channel")),
|
|
|
|
|
(".private", rtok("", ".private")),
|
|
|
|
|
("I-love-kebab", rtok("", "I-love-kebab")),
|
2021-11-04 21:22:35 +08:00
|
|
|
|
("but_snakes_is_also_good", rtok("", "but_snakes_is_also_good")),
|
2021-10-22 07:59:38 +08:00
|
|
|
|
("parens(", rtok("", "parens")),
|
|
|
|
|
("parens)", rtok("", "parens")),
|
|
|
|
|
("not!", rtok("", "not")),
|
|
|
|
|
(" channel", rtok(" ", "channel")),
|
|
|
|
|
("channel ", rtok("", "channel")),
|
2021-11-04 21:22:35 +08:00
|
|
|
|
(" channel ", rtok(" ", "channel")),
|
2021-10-22 07:59:38 +08:00
|
|
|
|
("'channel'", rtok("'", "channel")),
|
|
|
|
|
("\"channel\"", rtok("\"", "channel")),
|
|
|
|
|
("'cha)nnel'", rtok("'", "cha)nnel")),
|
|
|
|
|
("'cha\"nnel'", rtok("'", "cha\"nnel")),
|
|
|
|
|
("\"cha'nnel\"", rtok("\"", "cha'nnel")),
|
|
|
|
|
("\" some spaces \"", rtok("\"", " some spaces ")),
|
|
|
|
|
("\"cha'nnel\"", rtok("'", "cha'nnel")),
|
|
|
|
|
("\"cha'nnel\"", rtok("'", "cha'nnel")),
|
2021-11-08 22:30:26 +08:00
|
|
|
|
("I'm tamo", rtok("'m tamo", "I")),
|
2021-10-22 07:59:38 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (input, expected) in test_case {
|
2021-11-03 00:35:17 +08:00
|
|
|
|
let input = Span::new_extra(input, input);
|
2021-11-03 03:27:07 +08:00
|
|
|
|
let result = parse_value(input);
|
2021-10-22 07:59:38 +08:00
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
"Filter `{:?}` was supposed to be parsed but failed with the following error: `{}`",
|
|
|
|
|
expected,
|
|
|
|
|
result.unwrap_err()
|
|
|
|
|
);
|
|
|
|
|
let value = result.unwrap().1;
|
|
|
|
|
assert_eq!(value, expected, "Filter `{}` failed.", input);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-08 22:30:26 +08:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn diagnostic() {
|
|
|
|
|
let test_case = [
|
|
|
|
|
("🦀", "🦀"),
|
|
|
|
|
(" 🦀", "🦀"),
|
|
|
|
|
("🦀 AND crab = truc", "🦀"),
|
|
|
|
|
("🦀_in_name", "🦀_in_name"),
|
|
|
|
|
(" (name = ...", ""),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (input, expected) in test_case {
|
|
|
|
|
let input = Span::new_extra(input, input);
|
|
|
|
|
let result = parse_value(input);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_err(),
|
|
|
|
|
"Filter `{}` wasn’t supposed to be parsed but it did with the following result: `{:?}`",
|
|
|
|
|
expected,
|
|
|
|
|
result.unwrap()
|
|
|
|
|
);
|
|
|
|
|
// get the inner string referenced in the error
|
|
|
|
|
let value = *result.finish().unwrap_err().context().fragment();
|
|
|
|
|
assert_eq!(value, expected, "Filter `{}` was supposed to fail with the following value: `{}`, but it failed with: `{}`.", input, expected, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-22 07:59:38 +08:00
|
|
|
|
}
|