diff options
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | src/lib.rs | 65 | ||||
-rw-r--r-- | src/maybe.rs | 109 | ||||
-rw-r--r-- | tests/maybe.rs | 13 |
5 files changed, 127 insertions, 68 deletions
diff --git a/Cargo.toml b/Cargo.toml index 6b44765..69db765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde-util" -version = "0.1.0" +version = "0.2.0" authors = ["SoniEx2 <endermoneymod@gmail.com>"] license-file = "LICENSE.txt" description = "Soni's Serde Utilities" diff --git a/README.md b/README.md index 2a0dd34..9bb0377 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Soni's Serde Utilities This crate provides some utilities for use with serde. -Currently, it provides `MayBe<T>`, an `Option<T>`-like that doesn't error if -something is present but doesn't match a `T`. For example, it enables the JSON: +Currently, it provides `MayBe<T>`, a deserializable that doesn't error if +something doesn't match a `T`. For example, it enables the JSON: ```json { @@ -21,4 +21,4 @@ struct Foo { } ``` -as a `foo.bar.is_none()`. +as a `foo.bar.is_not()`. diff --git a/src/lib.rs b/src/lib.rs index 0002663..335a283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,60 +1,9 @@ -use serde::Deserialize; +//! Soni's Serde Utilities. +//! +//! This crate provides some utilities for use with [`serde`]. -#[derive(Deserialize)] -#[serde(untagged)] -enum MaybeHelper<T> { - Some(T), - None(serde::de::IgnoredAny), -} +pub use crate::maybe::MayBe; -/// Something that may be an `T`. -/// -/// Unlike `Option<T>`, this places no restriction on whether something -/// can't be something else entirely. -/// -/// Can only be used with self-describing formats, like JSON. -#[derive(Deserialize)] -#[serde(from = "Option<MaybeHelper<T>>")] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct MayBe<T>(pub Option<T>); - -impl<T> Default for MayBe<T> { - fn default() -> Self { - Self(Default::default()) - } -} - -impl<T> std::ops::Deref for MayBe<T> { - type Target = Option<T>; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<T> std::ops::DerefMut for MayBe<T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<T> From<MayBe<T>> for Option<T> { - fn from(thing: MayBe<T>) -> Option<T> { - thing.0 - } -} - -impl<T> From<Option<T>> for MayBe<T> { - fn from(thing: Option<T>) -> MayBe<T> { - Self(thing) - } -} - -impl<T> From<Option<MaybeHelper<T>>> for MayBe<T> { - fn from(thing: Option<MaybeHelper<T>>) -> MayBe<T> { - Self(match thing { - Some(MaybeHelper::Some(v)) => Some(v), - _ => None, - }) - } -} +mod maybe; +// TODO: stuff for use with MayBe + Vec/HashMap/BTreeMap/etc +//mod container_utils; diff --git a/src/maybe.rs b/src/maybe.rs new file mode 100644 index 0000000..9b2c419 --- /dev/null +++ b/src/maybe.rs @@ -0,0 +1,109 @@ +use serde::Deserialize; +use serde::Deserializer; + +/// Something that may be an `T`. +/// +/// If a value cannot be deserialized as a `T`, this will *discard* the value +/// and provide an `MayBe::IsNot`. +/// +/// Can only be used with self-describing formats, like JSON. +#[derive(Deserialize)] +#[serde(untagged)] +#[derive(Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum MayBe<T> { + Is(T), + #[serde(deserialize_with = "ignore_any")] + IsNot, +} + +fn ignore_any<'de, D: Deserializer<'de>>(d: D) -> Result<(), D::Error> { + serde::de::IgnoredAny::deserialize(d).map(|_| ()) +} + +// FIXME decide on Default +// This enum is analogous to Result, *not* Option. +// +//impl<T> Default for MayBe<T> { +// fn default() -> Self { +// MayBe::IsNot +// } +//} +//impl<T: Default> Default for MayBe<T> { +// fn default() -> Self { +// MayBe::Is(T::default()) +// } +//} + +impl<T> MayBe<T> { + /// Returns whether the `MayBe` contains a `T`. + pub fn is(&self) -> bool { + matches!(self, MayBe::Is(_)) + } + + /// Returns whether the `MayBe` does not contain a `T`. + pub fn is_not(&self) -> bool { + !self.is() + } + + /// Converts this `MayBe<T>` into an `Option<T>`. + pub fn into_opt(self) -> Option<T> { + self.into() + } + + /// Converts this `&MayBe<T>` into an `Option<&T>`. + pub fn as_opt(&self) -> Option<&T> { + match self { + MayBe::Is(t) => Some(t), + MayBe::IsNot => None, + } + } + + /// Converts this `&mut MayBe<T>` into an `Option<&mut T>`. + pub fn as_mut_opt(&mut self) -> Option<&mut T> { + match self { + MayBe::Is(t) => Some(t), + MayBe::IsNot => None, + } + } +} + +impl<T: Clone> Clone for MayBe<T> { + fn clone(&self) -> Self { + match self { + MayBe::Is(t) => MayBe::Is(t.clone()), + MayBe::IsNot => MayBe::IsNot, + } + } + + fn clone_from(&mut self, source: &Self) { + match (self, source) { + (MayBe::Is(a), MayBe::Is(b)) => a.clone_from(b), + (MayBe::IsNot, MayBe::IsNot) => (), + (a, b) => *a = b.clone(), + } + } +} + +impl<T> From<MayBe<T>> for Option<T> { + fn from(thing: MayBe<T>) -> Option<T> { + match thing { + MayBe::Is(t) => Some(t), + MayBe::IsNot => None, + } + } +} + +impl<T> From<Option<T>> for MayBe<T> { + fn from(thing: Option<T>) -> MayBe<T> { + match thing { + Some(t) => MayBe::Is(t), + None => MayBe::IsNot, + } + } +} + +impl<T> From<T> for MayBe<T> { + fn from(thing: T) -> MayBe<T> { + MayBe::Is(thing) + } +} diff --git a/tests/maybe.rs b/tests/maybe.rs index 68db667..1437195 100644 --- a/tests/maybe.rs +++ b/tests/maybe.rs @@ -7,23 +7,24 @@ use serde_util::MayBe; #[test] fn test_is_t() { let json = "256"; - assert!(from_str::<MayBe<f64>>(json).unwrap().is_some()); + assert!(from_str::<MayBe<f64>>(json).unwrap().is()); } #[test] fn test_is_not_t() { let json = "{}"; - assert!(from_str::<MayBe<f64>>(json).unwrap().is_none()); + assert!(from_str::<MayBe<f64>>(json).unwrap().is_not()); } #[test] fn test_is_missing() { #[derive(Deserialize)] struct Foo { - bar: MayBe<f64>, + #[serde(rename = "bar")] + _bar: MayBe<f64>, } let json = "{}"; - assert!(from_str::<Foo>(json).unwrap().bar.is_none()); + assert!(from_str::<Foo>(json).is_err()); } #[test] @@ -33,7 +34,7 @@ fn test_t_in_struct() { bar: MayBe<f64>, } let json = "{\"bar\": 123}"; - assert!(from_str::<Foo>(json).unwrap().bar.is_some()); + assert!(from_str::<Foo>(json).unwrap().bar.is()); } @@ -44,5 +45,5 @@ fn test_not_t_in_struct() { bar: MayBe<f64>, } let json = "{\"bar\": []}"; - assert!(from_str::<Foo>(json).unwrap().bar.is_none()); + assert!(from_str::<Foo>(json).unwrap().bar.is_not()); } |