Choose the underlying deserialize implementation
I'd like to be able to choose between new and new_saturating for the implementation of deserialize, how could this be implemented?
#[serde(deserialize_with = "deserialize_saturating")]
field: YourBoundedInteger,
and
fn deserialize_saturating<D: Deserializer<'de>>(deserializer: D) -> Result<YourBoundedInteger, D::Error> {
// replace `u32` with the repr of your bounded integer
u32::deserialize(deserializer).map(YourBoundedInteger::new_saturating)
}
Thx, would a change to the crate to make this possible out-of-the-box require a new major version to be released? Or can it be achieved with a minor?
Hmm. There are multiple possible designs here, not mutually exclusive, and I’m not sure which would be best:
Option 1: direct support for macro-generated integers only
bounded_integer! {
#[deserialize_saturating]
struct YourInteger(-5, 5);
}
As I write this, I reälize that our current approach of simply forwarding all the attributes that exist in the original declaration to the output is unsound. We’ll have to remove that. Anyway, in the macro we can just look for a #[deserialize_saturating], set some deserialize_saturating: bool in the BoundedInteger struct and affect the generated code accordingly.
Option 2: opt-out for macro-generated integers
thus allowing people to write their own implementation of Deserialize
bounded_integer! {
#[no_serde] // maybe #[no_deserialize]/#[no_deserialize] as well?
// or maybe it should be spelt #[derive(!Deserialize)]? 🤔
struct YourInteger(-5, 5);
}
then people can write:
impl<'de> Deserialize<'de> for YourInteger {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
u32::deserialize(deserializer).map(Self::new_saturating)
}
}
Option 3: provide some kind of serde helper
This version works for both macro-generated and const-generic bounded integers.
// This trait duplicates, but does not supplant, the inherent functionality on bounded integers;
// it can’t supplant it because we don’t want people to have to import the trait to use bounded
// integers.
pub trait BoundedInteger: Ord + /* bunch of stuff */ + private::Sealed {
type Inner;
const MIN: Self;
const MAX: Self;
unsafe fn new_unchecked(inner: Self::Inner) -> Self { unsafe { transmute(inner) } }
fn new_saturating(inner: Self::Inner) -> Self { /* … */ }
}
pub mod serde {
pub fn de_saturating<T: BoundedInteger, D: Deserializer<'de>>(deserializer: D) -> Result<T, D::Error> {
Deserialize::deserialize(deserializer).map(T::new_saturating)
}
}
pub use serde::*;
which you can then use with #[serde(deserialize_with = "bounded_integer::de_saturating")].
I would probably accept a PR for any of these approaches. I think they’re all good to have and could co-exist.