diff --git a/lib/src/questions.rs b/lib/src/questions.rs index 812f440..7eb481f 100644 --- a/lib/src/questions.rs +++ b/lib/src/questions.rs @@ -1,4 +1,13 @@ -use serde_derive::{Deserialize, Serialize}; +use std::{ + fmt::{self, Formatter}, + marker::PhantomData, +}; + +use serde::{ + de::{MapAccess, SeqAccess, Visitor}, + Deserialize, Deserializer, +}; + use serde::ser::SerializeStruct; use bitflags::bitflags; @@ -45,33 +54,20 @@ bitflags! { } } -#[derive(Debug, Default, Clone, Deserialize, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct BatchInfo { - #[serde(default)] pub filename: String, - #[serde(default)] pub description: String, - #[serde(default)] pub author: String, - #[serde(default)] pub comment: String, - #[serde(default)] pub url: String, - #[serde(default)] pub date: String, - #[serde(default)] pub processed_by: String, - #[serde(default)] pub redacted_by: String, - #[serde(default)] pub copyright: String, - #[serde(default)] pub theme: String, - #[serde(default)] pub kind: String, - #[serde(default)] pub source: String, - #[serde(default)] pub rating: String, } @@ -144,17 +140,17 @@ impl serde::Serialize for BatchInfo { where S: serde::Serializer, { - let is_human_readable = serializer.is_human_readable(); + let is_human_readable = serializer.is_human_readable(); let mut flags: BatchFlags = Default::default(); let mut len = 1; // (+flags) count_string_fields!((self, flags, len, BatchFlags) <- { - filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, - processed_by: PROCESSED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING + filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED, + redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING }); let mut state = serializer.serialize_struct("BatchInfo", len)?; - + if is_human_readable { state.skip_field("_flags")?; } else { @@ -162,8 +158,8 @@ impl serde::Serialize for BatchInfo { } serialize_fields!((self, flags, state, BatchFlags) <- { - filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, - processed_by: PROCESSED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING + filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED, + redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING }); state.end() @@ -175,7 +171,7 @@ impl serde::Serialize for Question { where S: serde::Serializer, { - let is_human_readable = serializer.is_human_readable(); + let is_human_readable = serializer.is_human_readable(); let mut flags: QuestionFlags = Default::default(); let mut len = 6; //(_flags + id + description + answer + num + batch_info) @@ -194,7 +190,7 @@ impl serde::Serialize for Question { }); let mut state = serializer.serialize_struct("Question", len)?; - + if is_human_readable { state.skip_field("_flags")?; } else { @@ -227,6 +223,269 @@ impl serde::Serialize for Question { } } +impl<'de> Deserialize<'de> for BatchInfo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[allow(non_camel_case_types)] + #[doc(hidden)] + enum BatchField { + filename, + description, + author, + comment, + url, + date, + processed_by, + redacted_by, + copyright, + theme, + kind, + source, + rating, + _flags, + __ignore, + } + + struct BatchFieldVisitor; + impl<'de> Visitor<'de> for BatchFieldVisitor { + type Value = BatchField; + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + Formatter::write_str(formatter, "field identifier") + } + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + match value { + 0u64 => Ok(BatchField::filename), + 1u64 => Ok(BatchField::description), + 2u64 => Ok(BatchField::author), + 3u64 => Ok(BatchField::comment), + 4u64 => Ok(BatchField::url), + 5u64 => Ok(BatchField::date), + 6u64 => Ok(BatchField::processed_by), + 7u64 => Ok(BatchField::redacted_by), + 8u64 => Ok(BatchField::copyright), + 9u64 => Ok(BatchField::theme), + 10u64 => Ok(BatchField::kind), + 11u64 => Ok(BatchField::source), + 12u64 => Ok(BatchField::rating), + 13u64 => Ok(BatchField::_flags), + _ => Ok(BatchField::__ignore), + } + } + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "filename" => Ok(BatchField::filename), + "description" => Ok(BatchField::description), + "author" => Ok(BatchField::author), + "comment" => Ok(BatchField::comment), + "url" => Ok(BatchField::url), + "date" => Ok(BatchField::date), + "processed_by" => Ok(BatchField::processed_by), + "redacted_by" => Ok(BatchField::redacted_by), + "copyright" => Ok(BatchField::copyright), + "theme" => Ok(BatchField::theme), + "kind" => Ok(BatchField::kind), + "source" => Ok(BatchField::source), + "rating" => Ok(BatchField::rating), + "_flags" => Ok(BatchField::_flags), + _ => Ok(BatchField::__ignore), + } + } + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + match value { + b"filename" => Ok(BatchField::filename), + b"description" => Ok(BatchField::description), + b"author" => Ok(BatchField::author), + b"comment" => Ok(BatchField::comment), + b"url" => Ok(BatchField::url), + b"date" => Ok(BatchField::date), + b"processed_by" => Ok(BatchField::processed_by), + b"redacted_by" => Ok(BatchField::redacted_by), + b"copyright" => Ok(BatchField::copyright), + b"theme" => Ok(BatchField::theme), + b"kind" => Ok(BatchField::kind), + b"source" => Ok(BatchField::source), + b"rating" => Ok(BatchField::rating), + b"_flags" => Ok(BatchField::_flags), + _ => Ok(BatchField::__ignore), + } + } + } + impl<'de> Deserialize<'de> for BatchField { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserializer::deserialize_identifier(deserializer, BatchFieldVisitor) + } + } + #[doc(hidden)] + struct BatchInfoVisitor<'de> { + marker: PhantomData, + lifetime: PhantomData<&'de ()>, + } + impl<'de> Visitor<'de> for BatchInfoVisitor<'de> { + type Value = BatchInfo; + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + Formatter::write_str(formatter, "struct BatchInfo") + } + #[inline] + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let _flags = BatchFlags::from_bits( + SeqAccess::next_element::(&mut seq)?.unwrap_or_default(), + ) + .unwrap_or_default(); + + macro_rules! seq_read_strings { + (($flags:ident, $seq:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$( + let $field: String = if $flags.intersects($FlagsType::$flag) { + SeqAccess::next_element::(&mut seq)?.unwrap_or_default() + } else { + Default::default() + }; + )+} + } + + seq_read_strings!((_flags, seq, BatchFlags) <- { + filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED, + redacted_by: REDACTED, copyright: COPYRIGHT, theme: THEME, kind: KIND, source: SOURCE, rating: RATING + }); + + Ok(BatchInfo { + filename, + description, + author, + comment, + url, + date, + processed_by, + redacted_by, + copyright, + theme, + kind, + source, + rating, + }) + } + #[inline] + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut filename: Option = None; + let mut description: Option = None; + let mut author: Option = None; + let mut comment: Option = None; + let mut url: Option = None; + let mut date: Option = None; + let mut processed_by: Option = None; + let mut redacted_by: Option = None; + let mut copyright: Option = None; + let mut theme: Option = None; + let mut kind: Option = None; + let mut source: Option = None; + let mut rating: Option = None; + + macro_rules! match_map_fields { + (($map:ident, $key:ident, $FieldType:ident) <- {$($field:ident),+} ) => { + match $key { + $( + $FieldType::$field => { + if $field.is_some() { + return Err(::duplicate_field( + std::stringify!($field), + )); + } + $field = Some(MapAccess::next_value::(&mut $map)?); + }, + )+ + _ => { + let _ = MapAccess::next_value::(&mut $map)?; + } + } + } + } + + while let Some(key) = MapAccess::next_key::(&mut map)? { + match_map_fields!((map, key, BatchField) <- { + filename, description, author, comment, url, date, processed_by, + redacted_by, copyright, theme, kind, source, rating + }); + } + + let filename = filename.unwrap_or_default(); + let description = description.unwrap_or_default(); + let author = author.unwrap_or_default(); + let comment = comment.unwrap_or_default(); + let url = url.unwrap_or_default(); + let date = date.unwrap_or_default(); + let processed_by = processed_by.unwrap_or_default(); + let redacted_by = redacted_by.unwrap_or_default(); + let copyright = copyright.unwrap_or_default(); + let theme = theme.unwrap_or_default(); + let kind = kind.unwrap_or_default(); + let source = source.unwrap_or_default(); + let rating = rating.unwrap_or_default(); + + Ok(BatchInfo { + filename, + description, + author, + comment, + url, + date, + processed_by, + redacted_by, + copyright, + theme, + kind, + source, + rating, + }) + } + } + #[doc(hidden)] + const FIELDS: &[&str] = &[ + "filename", + "description", + "author", + "comment", + "url", + "date", + "processed_by", + "redacted_by", + "copyright", + "theme", + "kind", + "source", + "rating", + ]; + Deserializer::deserialize_struct( + deserializer, + "BatchInfo", + FIELDS, + BatchInfoVisitor { + marker: PhantomData::, + lifetime: PhantomData, + }, + ) + } +} + #[cfg(any(feature = "convert", feature = "convert_async"))] pub mod convert_common { use super::{BatchInfo, Question};