From 9934825a932d7786bc292f8b4c2aff70824a0075 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 26 Aug 2023 00:08:14 +0300 Subject: [PATCH] questions: more macro_rules --- Cargo.lock | 7 ++ lib/Cargo.toml | 1 + lib/src/questions.rs | 285 ++++++++++++++++++++----------------------- 3 files changed, 142 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ccc828..19d9622 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,6 +228,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstringify" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd769563b4ea2953e2825c9e6b7470a5f55f67e0be00030bf3e390a2a6071f64" + [[package]] name = "bumpalo" version = "3.13.0" @@ -331,6 +337,7 @@ dependencies = [ "async-stream", "async_zip", "bitflags 2.4.0", + "bstringify", "fmmap", "futures", "futures-core", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 1feabaf..2d548d6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -48,6 +48,7 @@ memmap = { version = "0.7.0", optional = true } pin-project = { version = "1.1.3", optional = true } postcard = { version = "1.0.6", default-features = false } bitflags = "2.4.0" +bstringify = "0.1.2" [dev-dependencies] insta = { version = "1.31.0", features = ["yaml"] } diff --git a/lib/src/questions.rs b/lib/src/questions.rs index e0f83ee..d4ae82f 100644 --- a/lib/src/questions.rs +++ b/lib/src/questions.rs @@ -8,6 +8,7 @@ use serde::{ Deserialize, Deserializer, }; +use bstringify::bstringify; use serde::ser::SerializeStruct; use bitflags::bitflags; @@ -102,6 +103,12 @@ impl BatchInfo { } } +/// fill flags, if string field is not empty set bit flag +/// +/// Example: +/// ```ignore +/// flags_from_strings!((self, flags, BatchFlags) <- { filename: FILENAME, description: DESCRIPTION, ... }); +/// ``` macro_rules! flags_from_strings { (($self:ident, $flags:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$( if !$self.$field.is_empty() { @@ -110,6 +117,12 @@ macro_rules! flags_from_strings { )+} } +/// serialize fields, if bit flag is not set then skip +/// +/// Example: +/// ```ignore +/// serialize_fields!((self, flags, state, BatchFlags) <- { filename: FILENAME, description: DESCRIPTION, ... }); +/// ``` macro_rules! serialize_fields { (($self:ident, $flags:ident, $state:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$( if $flags.intersects($FlagsType::$flag) { @@ -208,6 +221,105 @@ impl serde::Serialize for Question { } } +/// match field name on _FieldVisitor::visit_str or ::visit_bytes +/// +/// Examples: +/// +/// str: +/// ```ignore +/// fn visit_str(self, value: &str) -> Result +/// where E: serde::de::Error, { +/// match_visit_str_fields!(value => BatchField {filename, description, ...}) +/// } +/// ``` +/// +/// or bytes: +/// ```ignore +/// fn visit_bytes(self, value: &[u8]) -> Result +/// where E: serde::de::Error, { +/// match_visit_str_fields!([bytes] value => BatchField {filename, description, ...}) +/// } +/// ``` +macro_rules! match_visit_str_fields { + ($value:ident => $FieldType:ty {$($field:ident),+}) => { + match $value { + $( + std::stringify!($field) => Ok(<$FieldType>::$field), + )+ + _ => Ok(<$FieldType>::__ignore), + } + }; + ([bytes] $value:ident => $FieldType:ty {$($field:ident),+}) => { + match $value { + $( + bstringify!($field) => Ok(<$FieldType>::$field), + )+ + _ => Ok(<$FieldType>::__ignore), + } +} +} + +/// for seq access, deserialize string fields if bit flag is set, else read as default (empty) +macro_rules! seq_read_strings { + (($flags:ident, $seq:ident, $FlagsType:ty) <- {$($field:ident:$flag:ident),+} ) => {$( + let $field: String = if $flags.intersects(<$FlagsType>::$flag) { + SeqAccess::next_element::(&mut $seq)?.unwrap_or_default() + } else { + Default::default() + }; + )+} +} + +/// for map access, match and deserialize fields + check for duplicated fields +/// +/// Examples: +/// +/// variant for mixed value types: +/// ```ignore +/// match_map_fields!((map, key, QuestionField) <- {num: u32, id: String, ...}); +/// ``` +/// +/// variant for single value type: +/// ```ignore +/// match_map_fields!((map, key, BatchField, String) <- {filename, url, ...}); +/// ``` +macro_rules! match_map_fields { + (($map:ident, $key:ident, $FieldType:ty) <- {$($field:ident:$FieldValueType:ty),+} ) => { + match $key { + $( + <$FieldType>::$field => { + if $field.is_some() { + return Err(::duplicate_field( + std::stringify!($field), + )); + } + $field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?); + }, + )+ + _ => { + let _ = MapAccess::next_value::(&mut $map)?; + } + } + }; + (($map:ident, $key:ident, $FieldType:ty, $FieldValueType:ty) <- {$($field:ident),+} ) => { + match $key { + $( + <$FieldType>::$field => { + if $field.is_some() { + return Err(::duplicate_field( + std::stringify!($field), + )); + } + $field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?); + }, + )+ + _ => { + let _ = MapAccess::next_value::(&mut $map)?; + } + } +} +} + impl<'de> Deserialize<'de> for BatchInfo { fn deserialize(deserializer: D) -> Result where @@ -264,45 +376,19 @@ impl<'de> Deserialize<'de> for BatchInfo { 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), - } + match_visit_str_fields!(value => BatchField { + filename, description, author, comment, url, date, processed_by, redacted_by, + copyright, theme, kind, source, rating, _flags, __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), - } + match_visit_str_fields!([bytes] value => BatchField { + filename, description, author, comment, url, date, processed_by, redacted_by, + copyright, theme, kind, source, rating, _flags, __ignore + }) } } impl<'de> Deserialize<'de> for BatchField { @@ -333,16 +419,6 @@ impl<'de> Deserialize<'de> for BatchInfo { ) .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 @@ -383,28 +459,8 @@ impl<'de> Deserialize<'de> for BatchInfo { 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) <- { + match_map_fields!((map, key, BatchField, String) <- { filename, description, author, comment, url, date, processed_by, redacted_by, copyright, theme, kind, source, rating }); @@ -534,55 +590,19 @@ impl<'de> Deserialize<'de> for Question { where E: serde::de::Error, { - match value { - "num" => Ok(QuestionField::num), - "id" => Ok(QuestionField::id), - "description" => Ok(QuestionField::description), - "answer" => Ok(QuestionField::answer), - "author" => Ok(QuestionField::author), - "comment" => Ok(QuestionField::comment), - "comment1" => Ok(QuestionField::comment1), - "tour" => Ok(QuestionField::tour), - "url" => Ok(QuestionField::url), - "date" => Ok(QuestionField::date), - "processed_by" => Ok(QuestionField::processed_by), - "redacted_by" => Ok(QuestionField::redacted_by), - "copyright" => Ok(QuestionField::copyright), - "theme" => Ok(QuestionField::theme), - "kind" => Ok(QuestionField::kind), - "source" => Ok(QuestionField::source), - "rating" => Ok(QuestionField::rating), - "batch_info" => Ok(QuestionField::batch_info), - "_flags" => Ok(QuestionField::_flags), - _ => Ok(QuestionField::__ignore), - } + match_visit_str_fields!(value => QuestionField { + num, id, description, answer, author, comment, comment1, tour, url, date, processed_by, + redacted_by, copyright, theme, kind, source, rating, batch_info, _flags, __ignore + }) } fn visit_bytes(self, value: &[u8]) -> Result where E: serde::de::Error, { - match value { - b"num" => Ok(QuestionField::num), - b"id" => Ok(QuestionField::id), - b"description" => Ok(QuestionField::description), - b"answer" => Ok(QuestionField::answer), - b"author" => Ok(QuestionField::author), - b"comment" => Ok(QuestionField::comment), - b"comment1" => Ok(QuestionField::comment1), - b"tour" => Ok(QuestionField::tour), - b"url" => Ok(QuestionField::url), - b"date" => Ok(QuestionField::date), - b"processed_by" => Ok(QuestionField::processed_by), - b"redacted_by" => Ok(QuestionField::redacted_by), - b"copyright" => Ok(QuestionField::copyright), - b"theme" => Ok(QuestionField::theme), - b"kind" => Ok(QuestionField::kind), - b"source" => Ok(QuestionField::source), - b"rating" => Ok(QuestionField::rating), - b"batch_info" => Ok(QuestionField::batch_info), - b"_flags" => Ok(QuestionField::_flags), - _ => Ok(QuestionField::__ignore), - } + match_visit_str_fields!([bytes] value => QuestionField { + num, id, description, answer, author, comment, comment1, tour, url, date, processed_by, + redacted_by, copyright, theme, kind, source, rating, batch_info, _flags, __ignore + }) } } impl<'de> Deserialize<'de> for QuestionField { @@ -614,16 +634,6 @@ impl<'de> Deserialize<'de> for Question { ) .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() - }; - )+} - } - let num: u32 = if _flags.intersects(QuestionFlags::NUM) { SeqAccess::next_element::(&mut seq)?.unwrap_or_default() } else { @@ -700,26 +710,6 @@ impl<'de> Deserialize<'de> for Question { let mut rating: Option = None; let mut batch_info: Option = None; - macro_rules! match_map_fields { - (($map:ident, $key:ident, $FieldType:ident) <- {$($field:ident:$FieldValueType:ident),+} ) => { - match $key { - $( - $FieldType::$field => { - if $field.is_some() { - return Err(::duplicate_field( - std::stringify!($field), - )); - } - $field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?); - }, - )+ - _ => { - let _ = MapAccess::next_value::(&mut $map)?; - } - } - } - } - while let Some(key) = MapAccess::next_key::(&mut map)? { match_map_fields!((map, key, QuestionField) <- { num: u32, id: String, description: String, answer: String, author: String, comment: String, @@ -728,18 +718,11 @@ impl<'de> Deserialize<'de> for Question { }); } - let id = match id { - Some(value) => value, - _ => return Err(::missing_field("id")), - }; - let description = match description { - Some(value) => value, - _ => return Err(::missing_field("description")), - }; - let answer = match answer { - Some(value) => value, - _ => return Err(::missing_field("answer")), - }; + let id = id.ok_or(::missing_field("id"))?; + let description = description + .ok_or(::missing_field("description"))?; + let answer = + answer.ok_or(::missing_field("answer"))?; let num = num.unwrap_or_default(); let author = author.unwrap_or_default();