questions: more macro_rules
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Belyaev 2023-08-26 00:08:14 +03:00
parent 6a21543890
commit 9934825a93
3 changed files with 142 additions and 151 deletions

7
Cargo.lock generated
View File

@ -228,6 +228,12 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bstringify"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd769563b4ea2953e2825c9e6b7470a5f55f67e0be00030bf3e390a2a6071f64"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.13.0" version = "3.13.0"
@ -331,6 +337,7 @@ dependencies = [
"async-stream", "async-stream",
"async_zip", "async_zip",
"bitflags 2.4.0", "bitflags 2.4.0",
"bstringify",
"fmmap", "fmmap",
"futures", "futures",
"futures-core", "futures-core",

View File

@ -48,6 +48,7 @@ memmap = { version = "0.7.0", optional = true }
pin-project = { version = "1.1.3", optional = true } pin-project = { version = "1.1.3", optional = true }
postcard = { version = "1.0.6", default-features = false } postcard = { version = "1.0.6", default-features = false }
bitflags = "2.4.0" bitflags = "2.4.0"
bstringify = "0.1.2"
[dev-dependencies] [dev-dependencies]
insta = { version = "1.31.0", features = ["yaml"] } insta = { version = "1.31.0", features = ["yaml"] }

View File

@ -8,6 +8,7 @@ use serde::{
Deserialize, Deserializer, Deserialize, Deserializer,
}; };
use bstringify::bstringify;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use bitflags::bitflags; 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 { macro_rules! flags_from_strings {
(($self:ident, $flags:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$( (($self:ident, $flags:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
if !$self.$field.is_empty() { 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 { macro_rules! serialize_fields {
(($self:ident, $flags:ident, $state:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$( (($self:ident, $flags:ident, $state:ident, $FlagsType:ident) <- {$($field:ident:$flag:ident),+} ) => {$(
if $flags.intersects($FlagsType::$flag) { 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<E>(self, value: &str) -> Result<Self::Value, E>
/// where E: serde::de::Error, {
/// match_visit_str_fields!(value => BatchField {filename, description, ...})
/// }
/// ```
///
/// or bytes:
/// ```ignore
/// fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
/// 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::<String>(&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(<V::Error as serde::de::Error>::duplicate_field(
std::stringify!($field),
));
}
$field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?);
},
)+
_ => {
let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
}
}
};
(($map:ident, $key:ident, $FieldType:ty, $FieldValueType:ty) <- {$($field:ident),+} ) => {
match $key {
$(
<$FieldType>::$field => {
if $field.is_some() {
return Err(<V::Error as serde::de::Error>::duplicate_field(
std::stringify!($field),
));
}
$field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?);
},
)+
_ => {
let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
}
}
}
}
impl<'de> Deserialize<'de> for BatchInfo { impl<'de> Deserialize<'de> for BatchInfo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -264,45 +376,19 @@ impl<'de> Deserialize<'de> for BatchInfo {
where where
E: serde::de::Error, E: serde::de::Error,
{ {
match value { match_visit_str_fields!(value => BatchField {
"filename" => Ok(BatchField::filename), filename, description, author, comment, url, date, processed_by, redacted_by,
"description" => Ok(BatchField::description), copyright, theme, kind, source, rating, _flags, __ignore
"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<E>(self, value: &[u8]) -> Result<Self::Value, E> fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where where
E: serde::de::Error, E: serde::de::Error,
{ {
match value { match_visit_str_fields!([bytes] value => BatchField {
b"filename" => Ok(BatchField::filename), filename, description, author, comment, url, date, processed_by, redacted_by,
b"description" => Ok(BatchField::description), copyright, theme, kind, source, rating, _flags, __ignore
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 { impl<'de> Deserialize<'de> for BatchField {
@ -333,16 +419,6 @@ impl<'de> Deserialize<'de> for BatchInfo {
) )
.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::<String>(&mut $seq)?.unwrap_or_default()
} else {
Default::default()
};
)+}
}
seq_read_strings!((flags, seq, BatchFlags) <- { seq_read_strings!((flags, seq, BatchFlags) <- {
filename: FILENAME, description: DESCRIPTION, author: AUTHOR, comment: COMMENT, url: URL, date: DATE, processed_by: PROCESSED, 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 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<String> = None; let mut source: Option<String> = None;
let mut rating: Option<String> = None; let mut rating: Option<String> = None;
macro_rules! match_map_fields {
(($map:ident, $key:ident, $FieldType:ident) <- {$($field:ident),+} ) => {
match $key {
$(
$FieldType::$field => {
if $field.is_some() {
return Err(<V::Error as serde::de::Error>::duplicate_field(
std::stringify!($field),
));
}
$field = Some(MapAccess::next_value::<String>(&mut $map)?);
},
)+
_ => {
let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
}
}
}
}
while let Some(key) = MapAccess::next_key::<BatchField>(&mut map)? { while let Some(key) = MapAccess::next_key::<BatchField>(&mut map)? {
match_map_fields!((map, key, BatchField) <- { match_map_fields!((map, key, BatchField, String) <- {
filename, description, author, comment, url, date, processed_by, filename, description, author, comment, url, date, processed_by,
redacted_by, copyright, theme, kind, source, rating redacted_by, copyright, theme, kind, source, rating
}); });
@ -534,55 +590,19 @@ impl<'de> Deserialize<'de> for Question {
where where
E: serde::de::Error, E: serde::de::Error,
{ {
match value { match_visit_str_fields!(value => QuestionField {
"num" => Ok(QuestionField::num), num, id, description, answer, author, comment, comment1, tour, url, date, processed_by,
"id" => Ok(QuestionField::id), redacted_by, copyright, theme, kind, source, rating, batch_info, _flags, __ignore
"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),
}
} }
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E> fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where where
E: serde::de::Error, E: serde::de::Error,
{ {
match value { match_visit_str_fields!([bytes] value => QuestionField {
b"num" => Ok(QuestionField::num), num, id, description, answer, author, comment, comment1, tour, url, date, processed_by,
b"id" => Ok(QuestionField::id), redacted_by, copyright, theme, kind, source, rating, batch_info, _flags, __ignore
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),
}
} }
} }
impl<'de> Deserialize<'de> for QuestionField { impl<'de> Deserialize<'de> for QuestionField {
@ -614,16 +634,6 @@ impl<'de> Deserialize<'de> for Question {
) )
.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::<String>(&mut $seq)?.unwrap_or_default()
} else {
Default::default()
};
)+}
}
let num: u32 = if _flags.intersects(QuestionFlags::NUM) { let num: u32 = if _flags.intersects(QuestionFlags::NUM) {
SeqAccess::next_element::<u32>(&mut seq)?.unwrap_or_default() SeqAccess::next_element::<u32>(&mut seq)?.unwrap_or_default()
} else { } else {
@ -700,26 +710,6 @@ impl<'de> Deserialize<'de> for Question {
let mut rating: Option<String> = None; let mut rating: Option<String> = None;
let mut batch_info: Option<BatchInfo> = None; let mut batch_info: Option<BatchInfo> = 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(<V::Error as serde::de::Error>::duplicate_field(
std::stringify!($field),
));
}
$field = Some(MapAccess::next_value::<$FieldValueType>(&mut $map)?);
},
)+
_ => {
let _ = MapAccess::next_value::<serde::de::IgnoredAny>(&mut $map)?;
}
}
}
}
while let Some(key) = MapAccess::next_key::<QuestionField>(&mut map)? { while let Some(key) = MapAccess::next_key::<QuestionField>(&mut map)? {
match_map_fields!((map, key, QuestionField) <- { match_map_fields!((map, key, QuestionField) <- {
num: u32, id: String, description: String, answer: String, author: String, comment: String, 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 { let id = id.ok_or(<V::Error as serde::de::Error>::missing_field("id"))?;
Some(value) => value, let description = description
_ => return Err(<V::Error as serde::de::Error>::missing_field("id")), .ok_or(<V::Error as serde::de::Error>::missing_field("description"))?;
}; let answer =
let description = match description { answer.ok_or(<V::Error as serde::de::Error>::missing_field("answer"))?;
Some(value) => value,
_ => return Err(<V::Error as serde::de::Error>::missing_field("description")),
};
let answer = match answer {
Some(value) => value,
_ => return Err(<V::Error as serde::de::Error>::missing_field("answer")),
};
let num = num.unwrap_or_default(); let num = num.unwrap_or_default();
let author = author.unwrap_or_default(); let author = author.unwrap_or_default();