Skip to content

Commit

Permalink
Fix the deserialization error of FieldEntry when the 'options' field …
Browse files Browse the repository at this point in the history
…appears before the 'type' field (#1199)

Co-authored-by: quel <azerowall>
  • Loading branch information
azerowall authored Nov 10, 2021
1 parent b75d4e5 commit fcff915
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 140 deletions.
162 changes: 23 additions & 139 deletions src/schema/field_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ use crate::schema::{is_valid_field_name, IntOptions};

use crate::schema::bytes_options::BytesOptions;
use crate::schema::FieldType;
use serde::de::{self, MapAccess, Visitor};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use serde::{Deserialize, Serialize};

/// A `FieldEntry` represents a field and its configuration.
/// `Schema` are a collection of `FieldEntry`
Expand All @@ -16,9 +13,10 @@ use std::fmt;
/// - a field name
/// - a field type, itself wrapping up options describing
/// how the field should be indexed.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FieldEntry {
name: String,
#[serde(flatten)]
field_type: FieldType,
}

Expand Down Expand Up @@ -141,140 +139,6 @@ impl FieldEntry {
}
}

impl Serialize for FieldEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("field_entry", 3)?;
s.serialize_field("name", &self.name)?;

match self.field_type {
FieldType::Str(ref options) => {
s.serialize_field("type", "text")?;
s.serialize_field("options", options)?;
}
FieldType::U64(ref options) => {
s.serialize_field("type", "u64")?;
s.serialize_field("options", options)?;
}
FieldType::I64(ref options) => {
s.serialize_field("type", "i64")?;
s.serialize_field("options", options)?;
}
FieldType::F64(ref options) => {
s.serialize_field("type", "f64")?;
s.serialize_field("options", options)?;
}
FieldType::Date(ref options) => {
s.serialize_field("type", "date")?;
s.serialize_field("options", options)?;
}
FieldType::HierarchicalFacet(ref options) => {
s.serialize_field("type", "hierarchical_facet")?;
s.serialize_field("options", options)?;
}
FieldType::Bytes(ref options) => {
s.serialize_field("type", "bytes")?;
s.serialize_field("options", options)?;
}
}

s.end()
}
}

impl<'de> Deserialize<'de> for FieldEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Name,
Type,
Options,
}

const FIELDS: &[&str] = &["name", "type", "options"];

struct FieldEntryVisitor;

impl<'de> Visitor<'de> for FieldEntryVisitor {
type Value = FieldEntry;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("struct FieldEntry")
}

fn visit_map<V>(self, mut map: V) -> Result<FieldEntry, V::Error>
where
V: MapAccess<'de>,
{
let mut name = None;
let mut ty = None;
let mut field_type = None;
while let Some(key) = map.next_key()? {
match key {
Field::Name => {
if name.is_some() {
return Err(de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
}
Field::Type => {
if ty.is_some() {
return Err(de::Error::duplicate_field("type"));
}
let type_string = map.next_value::<String>()?;
match type_string.as_str() {
"text" | "u64" | "i64" | "f64" | "date" | "bytes"
| "hierarchical_facet" => {
// These types require additional options to create a field_type
}
_ => panic!("unhandled type"),
}
ty = Some(type_string);
}
Field::Options => match ty {
None => {
let msg = "The `type` field must be \
specified before `options`";
return Err(de::Error::custom(msg));
}
Some(ref ty) => match ty.as_str() {
"text" => field_type = Some(FieldType::Str(map.next_value()?)),
"u64" => field_type = Some(FieldType::U64(map.next_value()?)),
"i64" => field_type = Some(FieldType::I64(map.next_value()?)),
"f64" => field_type = Some(FieldType::F64(map.next_value()?)),
"date" => field_type = Some(FieldType::Date(map.next_value()?)),
"bytes" => field_type = Some(FieldType::Bytes(map.next_value()?)),
"hierarchical_facet" => {
field_type =
Some(FieldType::HierarchicalFacet(map.next_value()?))
}
_ => {
let msg = format!("Unrecognised type {}", ty);
return Err(de::Error::custom(msg));
}
},
},
}
}

let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
ty.ok_or_else(|| de::Error::missing_field("ty"))?;
let field_type = field_type.ok_or_else(|| de::Error::missing_field("options"))?;

Ok(FieldEntry { name, field_type })
}
}

deserializer.deserialize_struct("field_entry", FIELDS, FieldEntryVisitor)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -315,4 +179,24 @@ mod tests {
_ => panic!("expected FieldType::Str"),
}
}

#[test]
fn test_json_deserialization() {
let json_str = r#"{
"name": "title",
"options": {
"indexing": {
"record": "position",
"tokenizer": "default"
},
"stored": false
},
"type": "text"
}"#;
let field_entry: FieldEntry = serde_json::from_str(json_str).unwrap();
match field_entry.field_type {
FieldType::Str(_) => {}
_ => panic!("expected FieldType::Str")
}
}
}
6 changes: 5 additions & 1 deletion src/schema/field_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::schema::Value;
use crate::schema::{IntOptions, TextOptions};
use crate::tokenizer::PreTokenizedString;
use chrono::{FixedOffset, Utc};
use serde::{Serialize, Deserialize};
use serde_json::Value as JsonValue;

/// Possible error that may occur while parsing a field value
Expand Down Expand Up @@ -48,9 +49,12 @@ pub enum Type {

/// A `FieldType` describes the type (text, u64) of a field as well as
/// how it should be handled by tantivy.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "options")]
#[serde(rename_all = "snake_case")]
pub enum FieldType {
/// String field type configuration
#[serde(rename = "text")]
Str(TextOptions),
/// Unsigned 64-bits integers field type configuration
U64(IntOptions),
Expand Down

0 comments on commit fcff915

Please sign in to comment.