mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-30 09:04:59 +08:00
Merge #380
380: Reserved keyword error message r=Kerollmops a=irevoire And I missed _another_ reserved keyword error message in the filter :( Co-authored-by: Tamo <tamo@meilisearch.com>
This commit is contained in:
commit
c9092c72bf
@ -20,12 +20,12 @@ impl fmt::Display for AscDescError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::InvalidSyntax { name } => {
|
Self::InvalidSyntax { name } => {
|
||||||
write!(f, "invalid asc/desc syntax for {}", name)
|
write!(f, "invalid asc/desc syntax for {}.", name)
|
||||||
}
|
}
|
||||||
Self::ReservedKeyword { name } => {
|
Self::ReservedKeyword { name } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} is a reserved keyword and thus can't be used as a asc/desc rule",
|
"{} is a reserved keyword and thus can't be used as a asc/desc rule.",
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -128,8 +128,6 @@ impl AscDesc {
|
|||||||
impl FromStr for AscDesc {
|
impl FromStr for AscDesc {
|
||||||
type Err = AscDescError;
|
type Err = AscDescError;
|
||||||
|
|
||||||
/// Since we don't know if this was deserialized for a criterion or a sort we just return a
|
|
||||||
/// string and let the caller create his own error.
|
|
||||||
fn from_str(text: &str) -> Result<AscDesc, Self::Err> {
|
fn from_str(text: &str) -> Result<AscDesc, Self::Err> {
|
||||||
match text.rsplit_once(':') {
|
match text.rsplit_once(':') {
|
||||||
Some((left, "asc")) => Ok(AscDesc::Asc(left.parse()?)),
|
Some((left, "asc")) => Ok(AscDesc::Asc(left.parse()?)),
|
||||||
@ -156,10 +154,10 @@ impl From<AscDescError> for SortError {
|
|||||||
SortError::BadGeoPointUsage { name }
|
SortError::BadGeoPointUsage { name }
|
||||||
}
|
}
|
||||||
AscDescError::ReservedKeyword { name } if &name == "_geo" => {
|
AscDescError::ReservedKeyword { name } if &name == "_geo" => {
|
||||||
SortError::ReservedNameForSettings { name: "_geoPoint".to_string() }
|
SortError::ReservedNameForSettings { name }
|
||||||
}
|
}
|
||||||
AscDescError::ReservedKeyword { name } if name.starts_with("_geoRadius") => {
|
AscDescError::ReservedKeyword { name } if name.starts_with("_geoRadius") => {
|
||||||
SortError::ReservedNameForFilter { name: "_geoRadius".to_string() }
|
SortError::ReservedNameForFilter { name: String::from("_geoRadius") }
|
||||||
}
|
}
|
||||||
AscDescError::ReservedKeyword { name } => SortError::ReservedName { name },
|
AscDescError::ReservedKeyword { name } => SortError::ReservedName { name },
|
||||||
}
|
}
|
||||||
@ -173,34 +171,26 @@ impl fmt::Display for SortError {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"invalid syntax for the `_geoPoint` parameter: `{}`. \
|
"invalid syntax for the `_geoPoint` parameter: `{}`. \
|
||||||
Usage: `_geoPoint(latitude, longitude):asc`",
|
Usage: `_geoPoint(latitude, longitude):asc`.",
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::InvalidName { name } => {
|
Self::InvalidName { name } => {
|
||||||
write!(f, "invalid syntax for the sort parameter {}", name)
|
write!(f, "invalid syntax for the sort parameter `{}`.", name)
|
||||||
}
|
}
|
||||||
Self::ReservedName { name } => {
|
Self::ReservedName { name } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} is a reserved keyword and thus can't be used as a sort expression",
|
"{} is a reserved keyword and thus can't be used as a sort expression.",
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::ReservedNameForSettings { name } => {
|
Self::ReservedNameForSettings { name } | Self::ReservedNameForFilter { name } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} is a reserved keyword and thus can't be used as a sort expression. \
|
"`{}` is a reserved keyword and thus can't be used as a sort expression. \
|
||||||
{} can only be used in the settings",
|
Use the `_geoPoint(latitude, longitude)` built-in rule to sort on `_geo` field coordinates.",
|
||||||
name, name
|
name,
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::ReservedNameForFilter { name } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} is a reserved keyword and thus can't be used as a sort expression. \
|
|
||||||
{} can only be used for filtering at search time",
|
|
||||||
name, name
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,4 +289,42 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sort_error_message() {
|
||||||
|
let errors = [
|
||||||
|
(
|
||||||
|
AscDescError::InvalidSyntax { name: S("truc:machin") },
|
||||||
|
S("invalid syntax for the sort parameter `truc:machin`."),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
AscDescError::InvalidSyntax { name: S("hello:world") },
|
||||||
|
S("invalid syntax for the sort parameter `hello:world`."),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
AscDescError::ReservedKeyword { name: S("_geo") },
|
||||||
|
S("`_geo` is a reserved keyword and thus can't be used as a sort expression. Use the `_geoPoint(latitude, longitude)` built-in rule to sort on `_geo` field coordinates."),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
AscDescError::ReservedKeyword { name: S("_geoDistance") },
|
||||||
|
S("_geoDistance is a reserved keyword and thus can't be used as a sort expression.")
|
||||||
|
),
|
||||||
|
(
|
||||||
|
AscDescError::ReservedKeyword { name: S("_geoRadius(12, 13)") },
|
||||||
|
S("`_geoRadius` is a reserved keyword and thus can't be used as a sort expression. Use the `_geoPoint(latitude, longitude)` built-in rule to sort on `_geo` field coordinates."),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (asc_desc_error, expected_message) in errors {
|
||||||
|
let sort_error = SortError::from(asc_desc_error);
|
||||||
|
assert_eq!(
|
||||||
|
sort_error.to_string(),
|
||||||
|
expected_message,
|
||||||
|
"was expecting {} for the error {:?} but instead got {}",
|
||||||
|
expected_message,
|
||||||
|
sort_error,
|
||||||
|
sort_error.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -600,24 +600,33 @@ fn field_id(
|
|||||||
// lexing ensures that we at least have a key
|
// lexing ensures that we at least have a key
|
||||||
let key = items.next().unwrap();
|
let key = items.next().unwrap();
|
||||||
if key.as_rule() == Rule::reserved {
|
if key.as_rule() == Rule::reserved {
|
||||||
return Err(PestError::new_from_span(
|
let message = match key.as_str() {
|
||||||
ErrorVariant::CustomError {
|
key if key.starts_with("_geoPoint") => {
|
||||||
message: format!(
|
format!(
|
||||||
"`{}` is a reserved keyword and therefore can't be used as a filter expression. \
|
"`_geoPoint` is a reserved keyword and thus can't be used as a filter expression. \
|
||||||
Available filterable attributes are: {}",
|
Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates.",
|
||||||
key.as_str(),
|
)
|
||||||
filterable_fields.iter().join(", "),
|
}
|
||||||
),
|
key @ "_geo" => {
|
||||||
},
|
format!(
|
||||||
key.as_span(),
|
"`{}` is a reserved keyword and thus can't be used as a filter expression. \
|
||||||
));
|
Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates.",
|
||||||
|
key
|
||||||
|
)
|
||||||
|
}
|
||||||
|
key => format!(
|
||||||
|
"`{}` is a reserved keyword and thus can't be used as a filter expression.",
|
||||||
|
key
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return Err(PestError::new_from_span(ErrorVariant::CustomError { message }, key.as_span()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filterable_fields.contains(key.as_str()) {
|
if !filterable_fields.contains(key.as_str()) {
|
||||||
return Err(PestError::new_from_span(
|
return Err(PestError::new_from_span(
|
||||||
ErrorVariant::CustomError {
|
ErrorVariant::CustomError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"attribute `{}` is not filterable, available filterable attributes are: {}",
|
"attribute `{}` is not filterable, available filterable attributes are: {}.",
|
||||||
key.as_str(),
|
key.as_str(),
|
||||||
filterable_fields.iter().join(", "),
|
filterable_fields.iter().join(", "),
|
||||||
),
|
),
|
||||||
@ -688,13 +697,6 @@ mod tests {
|
|||||||
let condition = FilterCondition::from_str(&rtxn, &index, "NOT channel = ponce").unwrap();
|
let condition = FilterCondition::from_str(&rtxn, &index, "NOT channel = ponce").unwrap();
|
||||||
let expected = Operator(0, Operator::NotEqual(None, S("ponce")));
|
let expected = Operator(0, Operator::NotEqual(None, S("ponce")));
|
||||||
assert_eq!(condition, expected);
|
assert_eq!(condition, expected);
|
||||||
|
|
||||||
let result = FilterCondition::from_str(&rtxn, &index, "_geo = France");
|
|
||||||
assert!(result.is_err());
|
|
||||||
let error = result.unwrap_err();
|
|
||||||
assert!(error.to_string().contains(
|
|
||||||
"`_geo` is a reserved keyword and therefore can't be used as a filter expression."
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -777,6 +779,49 @@ mod tests {
|
|||||||
assert_eq!(condition, expected);
|
assert_eq!(condition, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reserved_field_names() {
|
||||||
|
let path = tempfile::tempdir().unwrap();
|
||||||
|
let mut options = EnvOpenOptions::new();
|
||||||
|
options.map_size(10 * 1024 * 1024); // 10 MB
|
||||||
|
let index = Index::new(options, &path).unwrap();
|
||||||
|
let rtxn = index.read_txn().unwrap();
|
||||||
|
|
||||||
|
let error = FilterCondition::from_str(&rtxn, &index, "_geo = 12").unwrap_err();
|
||||||
|
assert!(error
|
||||||
|
.to_string()
|
||||||
|
.contains("`_geo` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates."),
|
||||||
|
"{}",
|
||||||
|
error.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error =
|
||||||
|
FilterCondition::from_str(&rtxn, &index, r#"_geoDistance <= 1000"#).unwrap_err();
|
||||||
|
assert!(error
|
||||||
|
.to_string()
|
||||||
|
.contains("`_geoDistance` is a reserved keyword and thus can't be used as a filter expression."),
|
||||||
|
"{}",
|
||||||
|
error.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error = FilterCondition::from_str(&rtxn, &index, r#"_geoPoint > 5"#).unwrap_err();
|
||||||
|
assert!(error
|
||||||
|
.to_string()
|
||||||
|
.contains("`_geoPoint` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates."),
|
||||||
|
"{}",
|
||||||
|
error.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error =
|
||||||
|
FilterCondition::from_str(&rtxn, &index, r#"_geoPoint(12, 16) > 5"#).unwrap_err();
|
||||||
|
assert!(error
|
||||||
|
.to_string()
|
||||||
|
.contains("`_geoPoint` is a reserved keyword and thus can't be used as a filter expression. Use the `_geoRadius(latitude, longitude, distance)` built-in rule to filter on `_geo` field coordinates."),
|
||||||
|
"{}",
|
||||||
|
error.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn geo_radius() {
|
fn geo_radius() {
|
||||||
let path = tempfile::tempdir().unwrap();
|
let path = tempfile::tempdir().unwrap();
|
||||||
@ -788,6 +833,20 @@ mod tests {
|
|||||||
let mut wtxn = index.write_txn().unwrap();
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
let mut builder = Settings::new(&mut wtxn, &index, 0);
|
let mut builder = Settings::new(&mut wtxn, &index, 0);
|
||||||
builder.set_searchable_fields(vec![S("_geo"), S("price")]); // to keep the fields order
|
builder.set_searchable_fields(vec![S("_geo"), S("price")]); // to keep the fields order
|
||||||
|
builder.execute(|_, _| ()).unwrap();
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
let rtxn = index.read_txn().unwrap();
|
||||||
|
// _geo is not filterable
|
||||||
|
let result = FilterCondition::from_str(&rtxn, &index, "_geoRadius(12, 12, 10)");
|
||||||
|
assert!(result.is_err());
|
||||||
|
let error = result.unwrap_err();
|
||||||
|
assert!(error
|
||||||
|
.to_string()
|
||||||
|
.contains("attribute `_geo` is not filterable, available filterable attributes are:"),);
|
||||||
|
|
||||||
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
let mut builder = Settings::new(&mut wtxn, &index, 0);
|
||||||
builder.set_filterable_fields(hashset! { S("_geo"), S("price") });
|
builder.set_filterable_fields(hashset! { S("_geo"), S("price") });
|
||||||
builder.execute(|_, _| ()).unwrap();
|
builder.execute(|_, _| ()).unwrap();
|
||||||
wtxn.commit().unwrap();
|
wtxn.commit().unwrap();
|
||||||
|
@ -8,7 +8,7 @@ char = _{ !(PEEK | "\\") ~ ANY
|
|||||||
| "\\" ~ (PEEK | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
|
| "\\" ~ (PEEK | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
|
||||||
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})}
|
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})}
|
||||||
|
|
||||||
reserved = { "_geo" | "_geoDistance" | "_geoPoint" | ("_geoPoint" ~ parameters) }
|
reserved = { "_geoDistance" | ("_geoPoint" ~ parameters) | "_geo" }
|
||||||
// we deliberately choose to allow empty parameters to generate more specific error message later
|
// we deliberately choose to allow empty parameters to generate more specific error message later
|
||||||
parameters = {("(" ~ (value ~ ",")* ~ value? ~ ")") | ""}
|
parameters = {("(" ~ (value ~ ",")* ~ value? ~ ")") | ""}
|
||||||
condition = _{between | eq | greater | less | geq | leq | neq}
|
condition = _{between | eq | greater | less | geq | leq | neq}
|
||||||
|
Loading…
Reference in New Issue
Block a user