use std::fmt;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::{SerializeMap, Serializer};
use serde::{Deserialize, Serialize};
#[allow(clippy::missing_docs_in_private_items)]
#[derive(Serialize, Deserialize)]
struct InnerTermQuery {
value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
boost: Option<f64>,
}
#[cfg(feature = "graphql")]
#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
#[derive(async_graphql::InputObject, Clone, Debug)]
#[graphql(name = "TermFilterInput")]
#[cfg_attr(feature = "builder", builder(field_defaults(setter(into))))]
pub struct TermQueryInput {
pub field: String,
pub value: String,
#[cfg_attr(feature = "builder", builder(default))]
pub boost: Option<f64>,
}
#[cfg(feature = "graphql")]
impl TermQueryInput {
#[inline]
pub fn new(field: impl Into<String>, value: impl Into<String>) -> Self {
TermQueryInput {
field: field.into(),
value: value.into(),
boost: None,
}
}
}
#[cfg(feature = "graphql")]
impl From<TermQuery> for TermQueryInput {
#[inline]
fn from(query: TermQuery) -> Self {
Self {
field: query.field,
value: query.value,
boost: query.boost,
}
}
}
#[cfg(feature = "graphql")]
impl Serialize for TermQueryInput {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(1))?;
let inner = InnerTermQuery {
value: self.value.to_owned(),
boost: self.boost,
};
map.serialize_entry(&self.field, &inner)?;
map.end()
}
}
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "graphql", derive(async_graphql::SimpleObject))]
#[cfg_attr(feature = "graphql", graphql(name = "TermFilter"))]
#[cfg_attr(feature = "builder", derive(typed_builder::TypedBuilder))]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "builder", builder(field_defaults(setter(into))))]
pub struct TermQuery {
pub field: String,
pub value: String,
#[cfg_attr(feature = "builder", builder(default))]
pub boost: Option<f64>,
}
impl TermQuery {
#[inline]
pub fn new(field: impl Into<String>, value: impl Into<String>) -> Self {
TermQuery {
field: field.into(),
value: value.into(),
boost: None,
}
}
}
#[cfg(feature = "graphql")]
impl From<TermQueryInput> for TermQuery {
#[inline]
fn from(input: TermQueryInput) -> TermQuery {
TermQuery {
field: input.field,
value: input.value,
boost: input.boost,
}
}
}
impl Serialize for TermQuery {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(1))?;
let inner = InnerTermQuery {
value: self.value.to_owned(),
boost: self.boost,
};
map.serialize_entry(&self.field, &inner)?;
map.end()
}
}
struct TermQueryVisitor;
impl<'de> serde::Deserialize<'de> for TermQuery {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<TermQuery, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(TermQueryVisitor)
}
}
impl<'de> Visitor<'de> for TermQueryVisitor {
type Value = TermQuery;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a `TermQuery`")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let field = map
.next_key::<String>()?
.ok_or_else(|| de::Error::missing_field("field"))?;
let inner: InnerTermQuery = map.next_value()?;
Ok(TermQuery {
field,
value: inner.value.to_owned(),
boost: inner.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::<TermQuery>($j).unwrap(), $f);
}
}
};
}
test_case!(
simple:
TermQuery {
field: "userProfile".to_string(),
value: "Kimchy".to_string(),
boost: None,
},
json!({ "userProfile": { "value": "Kimchy" } })
);
test_case!(
with_boost:
TermQuery {
field: "user".to_string(),
value: "Kimchy".to_string(),
boost: Some(1.1),
},
json!({ "user": { "value": "Kimchy", "boost": 1.1 } })
);
test_case!(
without_boost:
TermQuery {
field: "user".to_string(),
value: "Kimchy".to_string(),
boost: None,
},
json!({ "user": { "value": "Kimchy" } })
);
#[test]
fn deserialize_invalid_boost_is_err() {
let j = r#"{ "user": { "value": "Kimchy", "boost": "nan" } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "asdf" } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "1.x" } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": "x1" } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": "Kimchy", "boost": 2.0, "boost": "x1" } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
}
#[test]
fn deserialize_missing_values_is_err() {
let j = r#"{ "user": "missing" }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": null }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user" }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
}
#[test]
fn deserialize_invalid_values_is_err() {
let j = r#"{ "user": { "value": null } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": 1.1 } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": 1 } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": 999 } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": [null] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": ["Kimchy"] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": ["Kimchy", "elasticsearch"] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": [1.1] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": [1] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
let j = r#"{ "user": { "value": [999] } }"#;
assert!(serde_json::from_str::<TermQuery>(j).is_err(), "{}", &j);
}
}