make serde-str more convenient

This commit is contained in:
Trevor Spiteri 2020-10-20 12:10:49 +02:00
parent 9fdbb77c20
commit 8313677cb8
4 changed files with 70 additions and 106 deletions

View File

@ -78,6 +78,15 @@ The conversions supported cover the following cases.
## Whats new
### Version 1.4.0 news (unreleased)
* For the experimental feature [`serde-str`][feat-exp-1-4],
serialization in human-readable formats was made more convenient
to write manually. (This makes it incompatible with the version in
1.3.0.)
[feat-exp-1-4]: https://tspiteri.gitlab.io/fixed/dev/fixed/#experimental-optional-features
### Version 1.3.0 news (2020-10-15)
* The [`MulAssign`] implementation on fixed-point numbers now
@ -243,12 +252,12 @@ There are three experimental feature:
similar to how wrapping methods will wrap even when debug
assertions are enabled. (The plan is to always enable this
functionality but remove the experimental feature in version
1.4.0.)
1.5.0.)
3. `serde-str`, disabled by default. Fixed-point numbers are
serialized as strings showing the value when using human-readable
formats. This feature requires the `serde` and the `std` optional
features. (The plan is to upgrade this to an optional feature in
version 1.4.0.) **Warning:** numbers serialized when this feature
version 1.5.0.) **Warning:** numbers serialized when this feature
is enabled cannot be deserialized when this feature is disabled.
## License

View File

@ -5,6 +5,16 @@ modification, are permitted in any medium without royalty provided the
copyright notice and this notice are preserved. This file is offered
as-is, without any warranty. -->
Version 1.4.0 (unreleased)
==========================
* For the experimental feature [`serde-str`][feat-exp-1-4],
serialization in human-readable formats was made more convenient
to write manually. (This makes it incompatible with the version in
1.3.0.)
[feat-exp-1-4]: https://tspiteri.gitlab.io/fixed/dev/fixed/#experimental-optional-features
Version 1.3.0 (2020-10-15)
==========================

View File

@ -209,12 +209,12 @@ There are three experimental feature:
similar to how wrapping methods will wrap even when debug
assertions are enabled. (The plan is to always enable this
functionality but remove the experimental feature in version
1.4.0.)
1.5.0.)
3. `serde-str`, disabled by default. Fixed-point numbers are
serialized as strings showing the value when using human-readable
formats. This feature requires the `serde` and the `std` optional
features. (The plan is to upgrade this to an optional feature in
version 1.4.0.) **Warning:** numbers serialized when this feature
version 1.5.0.) **Warning:** numbers serialized when this feature
is enabled cannot be deserialized when this feature is disabled.
## License

View File

@ -20,56 +20,38 @@ use crate::{
FixedI128, FixedI16, FixedI32, FixedI64, FixedI8, FixedU128, FixedU16, FixedU32, FixedU64,
FixedU8, Wrapping,
};
use core::{
fmt::{Error as FmtError, Formatter, Result as FmtResult, Write as FmtWrite},
marker::PhantomData,
str,
};
#[cfg(feature = "serde-str")]
use serde::de::Unexpected;
use serde::{
de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor},
ser::{Serialize, SerializeStruct, Serializer},
de::{Deserialize, Deserializer, Error as DeError},
ser::{Serialize, Serializer},
};
#[cfg(not(feature = "serde-str"))]
use {
core::fmt::{Formatter, Result as FmtResult},
serde::{
de::{MapAccess, SeqAccess, Visitor},
ser::SerializeStruct,
},
};
// 42 bytes should be approximately enough for FixedI128:
// * log_10(2^128) == 39
// * one each for: sign, period, zero in case of I0F128
// To be safe and not care about corner cases, we just use 48 bytes.
struct Buffer {
len: usize,
data: [u8; 48],
}
impl FmtWrite for Buffer {
fn write_str(&mut self, s: &str) -> Result<(), FmtError> {
let next_len = self.len + s.len();
self.data[self.len..next_len].copy_from_slice(s.as_bytes());
self.len = next_len;
Ok(())
}
}
macro_rules! serde_fixed {
($Fixed:ident($LeEqU:ident) is $TBits:ident name $Name:expr) => {
impl<Frac: $LeEqU> Serialize for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let is_human_readable = serializer.is_human_readable();
let bits = self.to_bits();
let mut state = serializer.serialize_struct($Name, 1)?;
if cfg!(feature = "serde-str") && is_human_readable {
let mut buffer = Buffer {
len: 0,
data: [0; 48],
};
let _ = write!(buffer, "{}", self);
let string = str::from_utf8(&buffer.data[0..buffer.len]).expect("utf8");
state.serialize_field("value", string)?;
} else {
let bits = self.to_bits();
state.serialize_field("bits", &bits)?;
}
state.serialize_field("bits", &bits)?;
state.end()
}
#[cfg(feature = "serde-str")]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.to_bits().serialize(serializer)
}
}
}
impl<Frac: $LeEqU> Serialize for Wrapping<$Fixed<Frac>> {
@ -86,13 +68,11 @@ macro_rules! serde_fixed {
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct FixedVisitor<Frac: $LeEqU> {
phantom: PhantomData<Frac>,
is_human_readable: bool,
};
struct FixedVisitor;
impl<'de, Frac: $LeEqU> Visitor<'de> for FixedVisitor<Frac> {
impl<'de> Visitor<'de> for FixedVisitor {
type Value = $TBits;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
@ -101,21 +81,9 @@ macro_rules! serde_fixed {
}
fn visit_seq<V: SeqAccess<'de>>(self, mut seq: V) -> Result<$TBits, V::Error> {
#[cfg(feature = "serde-str")]
if self.is_human_readable {
let string: String = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let num: $Fixed<Frac> = string.parse().map_err(|_| {
de::Error::invalid_value(Unexpected::Str(&string), &self)
})?;
return Ok(num.to_bits());
}
// would be an unused variable if not serde-str
let _ = self.is_human_readable;
let bits = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
.ok_or_else(|| DeError::invalid_length(0, &self))?;
Ok(bits)
}
@ -125,44 +93,31 @@ macro_rules! serde_fixed {
match key {
Field::Bits => {
if bits.is_some() {
return Err(de::Error::duplicate_field("bits"));
return Err(DeError::duplicate_field("bits"));
}
bits = Some(map.next_value()?);
}
#[cfg(feature = "serde-str")]
Field::Value => {
if bits.is_some() {
return Err(de::Error::duplicate_field("value"));
}
let string: String = map.next_value()?;
let num: $Fixed<Frac> = string.parse().map_err(|_| {
de::Error::invalid_value(Unexpected::Str(&string), &self)
})?;
bits = Some(num.to_bits());
}
}
}
let missing = if cfg!(feature = "serde-str") {
"value"
} else {
"bits"
};
let bits = bits.ok_or_else(|| de::Error::missing_field(missing))?;
let bits = bits.ok_or_else(|| DeError::missing_field("bits"))?;
Ok(bits)
}
}
let is_human_readable = deserializer.is_human_readable();
let bits = deserializer.deserialize_struct(
$Name,
FIELDS,
FixedVisitor::<Frac> {
phantom: PhantomData,
is_human_readable,
},
)?;
let bits = deserializer.deserialize_struct($Name, FIELDS, FixedVisitor)?;
Ok($Fixed::from_bits(bits))
}
#[cfg(feature = "serde-str")]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
String::deserialize(deserializer)?
.parse()
.map_err(|e| DeError::custom(format_args!("parse error: {}", e)))
} else {
$TBits::deserialize(deserializer).map($Fixed::from_bits)
}
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for Wrapping<$Fixed<Frac>> {
@ -191,18 +146,15 @@ serde_fixed! { FixedU32(LeEqU32) is u32 name "FixedU32" }
serde_fixed! { FixedU64(LeEqU64) is u64 name "FixedU64" }
serde_fixed! { FixedU128(LeEqU128) is u128 name "FixedU128" }
const FIELDS: &[&str] = &[
"bits",
#[cfg(feature = "serde-str")]
"value",
];
#[cfg(not(feature = "serde-str"))]
const FIELDS: &[&str] = &["bits"];
#[cfg(not(feature = "serde-str"))]
enum Field {
Bits,
#[cfg(feature = "serde-str")]
Value,
}
#[cfg(not(feature = "serde-str"))]
impl<'de> Deserialize<'de> for Field {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Field, D::Error> {
struct FieldVisitor;
@ -211,20 +163,13 @@ impl<'de> Deserialize<'de> for Field {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
let s = if cfg!(feature = "serde-str") {
"`bits` or `value`"
} else {
"`bits`"
};
formatter.write_str(s)
formatter.write_str("`bits`")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Field, E> {
fn visit_str<E: DeError>(self, value: &str) -> Result<Field, E> {
match value {
"bits" => Ok(Field::Bits),
#[cfg(feature = "serde-str")]
"value" => Ok(Field::Value),
_ => Err(de::Error::unknown_field(value, FIELDS)),
_ => Err(DeError::unknown_field(value, FIELDS)),
}
}
}