questions: more macro_rules
continuous-integration/drone/push Build is passing Details

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",
]
[[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",

View File

@ -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"] }

View File

@ -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<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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: &[u8]) -> Result<Self::Value, E>
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::<String>(&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<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)? {
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<E>(self, value: &[u8]) -> Result<Self::Value, E>
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::<String>(&mut $seq)?.unwrap_or_default()
} else {
Default::default()
};
)+}
}
let num: u32 = if _flags.intersects(QuestionFlags::NUM) {
SeqAccess::next_element::<u32>(&mut seq)?.unwrap_or_default()
} else {
@ -700,26 +710,6 @@ impl<'de> Deserialize<'de> for Question {
let mut rating: Option<String> = 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)? {
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(<V::Error as serde::de::Error>::missing_field("id")),
};
let description = match description {
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 id = id.ok_or(<V::Error as serde::de::Error>::missing_field("id"))?;
let description = description
.ok_or(<V::Error as serde::de::Error>::missing_field("description"))?;
let answer =
answer.ok_or(<V::Error as serde::de::Error>::missing_field("answer"))?;
let num = num.unwrap_or_default();
let author = author.unwrap_or_default();