#[cfg(feature = "serdevalid")]
use paste;
#[cfg(feature = "serdevalid")]
use regex::Regex;
#[cfg(feature = "serdejson")]
use serde::de::Error as SerdeError;
#[cfg(feature = "serdejson")]
use serde::de::{Deserialize, DeserializeOwned, Deserializer};
#[cfg(feature = "serdejson")]
use serde::ser::{Serialize, Serializer};
#[cfg(feature = "serdevalid")]
use serde_valid::{validation, Validate};
use std::clone::Clone;
use std::mem;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Nullable<T> {
Null,
Present(T),
}
impl<T> Nullable<T> {
#[inline]
pub fn is_present(&self) -> bool {
match *self {
Nullable::Present(_) => true,
Nullable::Null => false,
}
}
#[inline]
pub fn is_null(&self) -> bool {
!self.is_present()
}
#[inline]
pub fn as_ref(&self) -> Nullable<&T> {
match *self {
Nullable::Present(ref x) => Nullable::Present(x),
Nullable::Null => Nullable::Null,
}
}
#[inline]
pub fn as_mut(&mut self) -> Nullable<&mut T> {
match *self {
Nullable::Present(ref mut x) => Nullable::Present(x),
Nullable::Null => Nullable::Null,
}
}
#[inline]
pub fn expect(self, msg: &str) -> T {
match self {
Nullable::Present(val) => val,
Nullable::Null => expect_failed(msg),
}
}
#[inline]
pub fn unwrap(self) -> T {
match self {
Nullable::Present(val) => val,
Nullable::Null => panic!("called `Nullable::unwrap()` on a `Nullable::Null` value"),
}
}
#[inline]
pub fn unwrap_or(self, def: T) -> T {
match self {
Nullable::Present(x) => x,
Nullable::Null => def,
}
}
#[inline]
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
match self {
Nullable::Present(x) => x,
Nullable::Null => f(),
}
}
#[inline]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Nullable<U> {
match self {
Nullable::Present(x) => Nullable::Present(f(x)),
Nullable::Null => Nullable::Null,
}
}
#[inline]
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
match self {
Nullable::Present(t) => f(t),
Nullable::Null => default,
}
}
#[inline]
pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
match self {
Nullable::Present(t) => f(t),
Nullable::Null => default(),
}
}
#[inline]
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
match self {
Nullable::Present(v) => Ok(v),
Nullable::Null => Err(err),
}
}
#[inline]
pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
match self {
Nullable::Present(v) => Ok(v),
Nullable::Null => Err(err()),
}
}
#[inline]
pub fn and<U>(self, optb: Nullable<U>) -> Nullable<U> {
match self {
Nullable::Present(_) => optb,
Nullable::Null => Nullable::Null,
}
}
#[inline]
pub fn and_then<U, F: FnOnce(T) -> Nullable<U>>(self, f: F) -> Nullable<U> {
match self {
Nullable::Present(x) => f(x),
Nullable::Null => Nullable::Null,
}
}
#[inline]
pub fn or(self, optb: Nullable<T>) -> Nullable<T> {
match self {
Nullable::Present(_) => self,
Nullable::Null => optb,
}
}
#[inline]
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
match self {
Nullable::Present(_) => self,
Nullable::Null => f(),
}
}
#[inline]
pub fn take(&mut self) -> Nullable<T> {
mem::replace(self, Nullable::Null)
}
}
impl<T: Clone> Nullable<&T> {
pub fn cloned(self) -> Nullable<T> {
self.map(Clone::clone)
}
}
impl<T: Default> Nullable<T> {
#[inline]
pub fn unwrap_or_default(self) -> T {
match self {
Nullable::Present(x) => x,
Nullable::Null => Default::default(),
}
}
}
#[inline(never)]
#[cold]
fn expect_failed(msg: &str) -> ! {
panic!("{}", msg)
}
impl<T> Default for Nullable<T> {
#[inline]
fn default() -> Nullable<T> {
Nullable::Null
}
}
impl<T> From<T> for Nullable<T> {
fn from(val: T) -> Nullable<T> {
Nullable::Present(val)
}
}
#[cfg(feature = "serdejson")]
impl<T> Serialize for Nullable<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Nullable::Present(ref inner) => serializer.serialize_some(&inner),
Nullable::Null => serializer.serialize_none(),
}
}
}
#[cfg(feature = "serdejson")]
impl<'de, T> Deserialize<'de> for Nullable<T>
where
T: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Nullable<T>, D::Error>
where
D: Deserializer<'de>,
{
let presence: Result<::serde_json::Value, _> =
::serde::Deserialize::deserialize(deserializer);
match presence {
Ok(::serde_json::Value::Null) => Ok(Nullable::Null),
Ok(some_value) => ::serde_json::from_value(some_value)
.map(Nullable::Present)
.map_err(SerdeError::custom),
Err(x) => Err(x),
}
}
}
#[cfg(feature = "serdevalid")]
impl<T> Validate for Nullable<T>
where
T: Validate,
{
fn validate(&self) -> Result<(), validation::Errors> {
match self {
Nullable::Present(val) => val.validate(),
Nullable::Null => Ok(()),
}
}
}
#[cfg(feature = "serdevalid")]
macro_rules! impl_generic_composited_validation_nullable {
(
$ErrorType:ident,
$limit_type:ty$(,)*
) => {
paste::paste! {
#[cfg(feature = "serdevalid")]
impl<T> validation::[<ValidateComposited $ErrorType >] for Nullable<T>
where
T: validation::[<ValidateComposited $ErrorType >],
{
fn [< validate_composited_ $ErrorType:snake>](
&self,
limit: $limit_type,
) -> Result<(), validation::Composited<serde_valid::[<$ErrorType Error>]>> {
match self {
Nullable::Present(val) => val.[< validate_composited_ $ErrorType:snake>](limit),
Nullable::Null => Ok(()),
}
}
}
}
};
(
$ErrorType:ident<T>
) => {
paste::paste! {
#[cfg(feature = "serdevalid")]
impl<T, U> validation::[<ValidateComposited $ErrorType >]<T> for Nullable<U>
where
T: Copy,
U: validation::[<ValidateComposited $ErrorType >]<T>,
{
fn [< validate_composited_ $ErrorType:snake>](
&self,
limit: T,
) -> Result<(), validation::Composited<serde_valid::[<$ErrorType Error>]>> {
match self {
Nullable::Present(val) => val.[< validate_composited_ $ErrorType:snake>](limit),
Nullable::Null => Ok(()),
}
}
}
}
};
}
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(Maximum<T>);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(Minimum<T>);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(ExclusiveMaximum<T>);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(ExclusiveMinimum<T>);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(MultipleOf<T>);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(MaxLength, usize);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(MinLength, usize);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(Pattern, &Regex);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(MaxProperties, usize);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(MinProperties, usize);
#[cfg(feature = "serdevalid")]
impl_generic_composited_validation_nullable!(Enumerate<T>);
pub fn default_optional_nullable<T>() -> Option<Nullable<T>> {
None
}
#[cfg(feature = "serdejson")]
pub fn deserialize_optional_nullable<'de, D, T>(
deserializer: D,
) -> Result<Option<Nullable<T>>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<T>::deserialize(deserializer).map(|val| match val {
Some(inner) => Some(Nullable::Present(inner)),
None => Some(Nullable::Null),
})
}
#[cfg(test)]
#[cfg(feature = "serdejson")]
mod serde_tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
struct NullableStringStruct {
item: Nullable<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OptionalNullableStringStruct {
#[serde(deserialize_with = "deserialize_optional_nullable")]
#[serde(default = "default_optional_nullable")]
#[serde(skip_serializing_if = "Option::is_none")]
item: Option<Nullable<String>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct NullableObjectStruct {
item: Nullable<NullableStringStruct>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OptionalNullableObjectStruct {
item: Option<Nullable<NullableStringStruct>>,
}
#[cfg(feature = "serdevalid")]
#[derive(Validate)]
struct ValidatedNullableItemsStruct {
#[validate(enumerate = [5, 10, 15])]
#[validate(minimum = 5)]
#[validate(maximum = 15)]
#[validate(exclusive_minimum = 4)]
#[validate(exclusive_maximum = 16)]
#[validate(multiple_of = 5)]
number: Nullable<i32>,
#[validate(pattern = "d")]
#[validate(max_length = 5)]
#[validate(min_length = 5)]
string: Nullable<String>,
}
macro_rules! round_trip {
($type:ty, $string:expr) => {
println!("Original: {:?}", $string);
let json: ::serde_json::Value =
::serde_json::from_str($string).expect("Deserialization to JSON Value failed");
println!("JSON Value: {:?}", json);
let thing: $type =
::serde_json::from_value(json.clone()).expect("Deserialization to struct failed");
println!("Struct: {:?}", thing);
let json_redux: ::serde_json::Value = ::serde_json::to_value(thing.clone())
.expect("Reserialization to JSON Value failed");
println!("JSON Redux: {:?}", json_redux);
let string_redux =
::serde_json::to_string(&thing).expect("Reserialziation to JSON String failed");
println!("String Redux: {:?}", string_redux);
assert_eq!(
$string, string_redux,
"Original did not match after round trip"
);
};
}
#[test]
fn missing_optionalnullable_value() {
let string = "{}";
round_trip!(OptionalNullableStringStruct, string);
}
#[test]
fn null_optionalnullable_value() {
let string = "{\"item\":null}";
round_trip!(OptionalNullableStringStruct, string);
}
#[test]
fn string_optionalnullable_value() {
let string = "{\"item\":\"abc\"}";
round_trip!(OptionalNullableStringStruct, string);
}
#[test]
fn object_optionalnullable_value() {
let string = "{\"item\":{\"item\":\"abc\"}}";
round_trip!(OptionalNullableObjectStruct, string);
}
#[test]
#[should_panic]
fn missing_nullable_value() {
let string = "{}";
round_trip!(NullableStringStruct, string);
}
#[test]
fn null_nullable_value() {
let string = "{\"item\":null}";
round_trip!(NullableStringStruct, string);
}
#[test]
fn string_nullable_value() {
let string = "{\"item\":\"abc\"}";
round_trip!(NullableStringStruct, string);
}
#[test]
fn object_nullable_value() {
let string = "{\"item\":{\"item\":\"abc\"}}";
round_trip!(NullableObjectStruct, string);
}
#[cfg(feature = "serdevalid")]
#[test]
fn validate_nullable_items() {
let valid = ValidatedNullableItemsStruct {
string: Nullable::Present("ddddd".to_string()),
number: Nullable::Present(5),
};
assert!(valid.validate().is_ok());
let valid_null = ValidatedNullableItemsStruct {
string: Nullable::Null,
number: Nullable::Null,
};
assert!(valid_null.validate().is_ok());
let invalid_low = ValidatedNullableItemsStruct {
string: Nullable::Present("f".to_string()),
number: Nullable::Present(3),
};
let errors_low = invalid_low.validate().unwrap_err().to_string();
assert_eq!(
errors_low,
serde_json::json!({
"errors":[
],
"properties":{
"string":{
"errors":[
"The value must match the pattern of \"d\".",
"The length of the value must be `>= 5`."
]
},
"number":{
"errors":[
"The value must be in [5, 10, 15].",
"The number must be `>= 5`.",
"The number must be `> 4`.",
"The value must be multiple of `5`."
]
}
}
})
.to_string()
);
let invalid_high = ValidatedNullableItemsStruct {
string: Nullable::Present("ddddddddd".to_string()),
number: Nullable::Present(25),
};
let errors_high = invalid_high.validate().unwrap_err().to_string();
assert_eq!(
errors_high,
serde_json::json!({
"errors":[
],
"properties":{
"string":{
"errors":[
"The length of the value must be `<= 5`."
]
},
"number":{
"errors":[
"The value must be in [5, 10, 15].",
"The number must be `<= 15`.",
"The number must be `< 16`.",
]
}
}
})
.to_string()
);
}
}