use std::fmt;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::{SerializeMap, Serializer};
use serde::Serialize;
#[cfg(feature = "graphql")]
#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
#[derive(async_graphql::InputObject, Clone, Debug)]
#[graphql(name = "TermsFilterInput")]
#[cfg_attr(feature = "builder", builder(field_defaults(setter(into))))]
pub struct TermsQueryInput {
pub field: String,
#[cfg_attr(feature = "builder", builder(default))]
pub values: Vec<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub boost: Option<f64>,
}
#[cfg(feature = "graphql")]
impl TermsQueryInput {
#[inline]
pub fn new<T: Into<String>>(
field: impl Into<String>,
values: impl IntoIterator<Item = T>,
) -> Self {
TermsQueryInput {
field: field.into(),
values: values.into_iter().map(Into::into).collect::<Vec<String>>(),
boost: None,
}
}
}
#[cfg(feature = "graphql")]
impl From<TermsQuery> for TermsQueryInput {
#[inline]
fn from(query: TermsQuery) -> Self {
Self {
field: query.field,
values: query.values,
boost: query.boost,
}
}
}
#[cfg(feature = "graphql")]
impl Serialize for TermsQueryInput {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry(&self.field, &self.values)?;
if let Some(boost) = &self.boost {
map.serialize_entry("boost", &boost)?;
}
map.end()
}
}
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
#[cfg_attr(feature = "graphql", graphql(name = "TermsFilter"))]
#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "builder", builder(field_defaults(setter(into))))]
pub struct TermsQuery {
pub field: String,
#[cfg_attr(feature = "builder", builder(default))]
pub values: Vec<String>,
#[cfg_attr(feature = "builder", builder(default))]
pub boost: Option<f64>,
}
impl TermsQuery {
#[inline]
pub fn new<T: Into<String>>(
field: impl Into<String>,
values: impl IntoIterator<Item = T>,
) -> Self {
TermsQuery {
field: field.into(),
values: values.into_iter().map(Into::into).collect::<Vec<String>>(),
boost: None,
}
}
}
#[cfg(feature = "graphql")]
impl From<TermsQueryInput> for TermsQuery {
#[inline]
fn from(input: TermsQueryInput) -> TermsQuery {
TermsQuery {
field: input.field,
values: input.values,
boost: input.boost,
}
}
}
impl Serialize for TermsQuery {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry(&self.field, &self.values)?;
if let Some(boost) = &self.boost {
map.serialize_entry("boost", &boost)?;
}
map.end()
}
}
struct TermsQueryVisitor;
impl<'de> serde::Deserialize<'de> for TermsQuery {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<TermsQuery, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(TermsQueryVisitor)
}
}
impl<'de> Visitor<'de> for TermsQueryVisitor {
type Value = TermsQuery;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `TermsQuery`")
}
fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let boost_field = "boost".to_string();
let mut field: Option<String> = None;
let mut values: Option<Vec<String>> = None;
let mut boost: Option<f64> = None;
while let Some(key) = access.next_key::<String>()? {
if key == boost_field {
if boost.is_some() {
return Err(de::Error::duplicate_field("boost"));
}
boost = Some(access.next_value::<f64>()?);
} else {
if field.is_some() {
return Err(de::Error::duplicate_field("field"));
}
field = Some(key);
values = Some(access.next_value::<Vec<String>>()?);
}
}
let field = field.ok_or_else(|| de::Error::missing_field("field"))?;
let values = values.ok_or_else(|| de::Error::missing_field("values"))?;
Ok(TermsQuery {
field,
values,
boost,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
macro_rules! test_case {
($name:ident : $f:expr, $j:expr) => {
mod $name {
use super::*;
#[test]
fn can_serialize() {
assert_eq!(serde_json::to_value(&$f).unwrap(), $j);
}
#[test]
fn can_deserialize() {
assert_eq!(serde_json::from_value::<TermsQuery>($j).unwrap(), $f);
}
}
};
}
test_case!(
simple:
TermsQuery {
field: "userProfile".to_string(),
values: vec!["Kimchy".to_string(), "elasticsearch".to_string()],
boost: None,
},
json!({ "userProfile": ["Kimchy", "elasticsearch"] })
);
test_case!(
with_boost:
TermsQuery {
field: "user".to_string(),
values: vec!["Kimchy".to_string(), "elasticsearch".to_string()],
boost: Some(1.1),
},
json!({ "user": ["Kimchy", "elasticsearch"], "boost": 1.1 })
);
test_case!(
without_boost:
TermsQuery {
field: "user".to_string(),
values: vec!["Kimchy".to_string(), "elasticsearch".to_string()],
boost: None,
},
json!({ "user": ["Kimchy", "elasticsearch"] })
);
#[test]
fn deserialize_invalid_boost_is_err() {
let j = r#"{ "user": { "value": "Kimchy", "boost": "nan" } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "asdf" } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "1.x" } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "x1" } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": 2.0, "boost": "x1" } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
}
#[test]
fn deserialize_missing_values_is_err() {
let j = r#"{ "user": "missing" }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": null }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user" }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
}
#[test]
fn deserialize_invalid_values_is_err() {
let j = r#"{ "user": { "value": 1.1 } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": 1 } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": 999 } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "values": [null] } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "values": [1.1] } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "values": [1] } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "values": 999 } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "values": null } }"#;
assert!(serde_json::from_str::<TermsQuery>(j).is_err(), "{}", &j);
}
}