use std::{borrow::Cow, ops::Deref}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{registry, InputType, InputValueError, InputValueResult, Value}; /// Similar to `Option`, but it has three states, `undefined`, `null` and `x`. /// /// **Reference:** /// /// # Examples /// /// ```rust /// use async_graphql::*; /// /// struct Query; /// /// #[Object] /// impl Query { /// async fn value1(&self, input: MaybeUndefined) -> i32 { /// if input.is_null() { /// 1 /// } else if input.is_undefined() { /// 2 /// } else { /// input.take().unwrap() /// } /// } /// } /// /// # tokio::runtime::Runtime::new().unwrap().block_on(async { /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); /// let query = r#" /// { /// v1:value1(input: 99) /// v2:value1(input: null) /// v3:value1 /// }"#; /// assert_eq!( /// schema.execute(query).await.into_result().unwrap().data, /// value!({ /// "v1": 99, /// "v2": 1, /// "v3": 2, /// }) /// ); /// # }); /// ``` #[allow(missing_docs)] #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] pub enum MaybeUndefined { Undefined, Null, Value(T), } impl Default for MaybeUndefined { fn default() -> Self { Self::Undefined } } impl MaybeUndefined { /// Returns true if the `MaybeUndefined` is undefined. #[inline] pub const fn is_undefined(&self) -> bool { matches!(self, MaybeUndefined::Undefined) } /// Returns true if the `MaybeUndefined` is null. #[inline] pub const fn is_null(&self) -> bool { matches!(self, MaybeUndefined::Null) } /// Returns true if the `MaybeUndefined` contains value. #[inline] pub const fn is_value(&self) -> bool { matches!(self, MaybeUndefined::Value(_)) } /// Borrow the value, returns `None` if the the `MaybeUndefined` is /// `undefined` or `null`, otherwise returns `Some(T)`. #[inline] pub const fn value(&self) -> Option<&T> { match self { MaybeUndefined::Value(value) => Some(value), _ => None, } } /// Converts the `MaybeUndefined` to `Option`. #[inline] pub fn take(self) -> Option { match self { MaybeUndefined::Value(value) => Some(value), _ => None, } } /// Converts the `MaybeUndefined` to `Option>`. #[inline] pub const fn as_opt_ref(&self) -> Option> { match self { MaybeUndefined::Undefined => None, MaybeUndefined::Null => Some(None), MaybeUndefined::Value(value) => Some(Some(value)), } } /// Converts the `MaybeUndefined` to `Option>`. #[inline] pub fn as_opt_deref(&self) -> Option> where U: ?Sized, T: Deref, { match self { MaybeUndefined::Undefined => None, MaybeUndefined::Null => Some(None), MaybeUndefined::Value(value) => Some(Some(value.deref())), } } /// Returns `true` if the `MaybeUndefined` contains the given value. #[inline] pub fn contains_value(&self, x: &U) -> bool where U: PartialEq, { match self { MaybeUndefined::Value(y) => x == y, _ => false, } } /// Returns `true` if the `MaybeUndefined` contains the given nullable /// value. #[inline] pub fn contains(&self, x: &Option) -> bool where U: PartialEq, { match self { MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y), MaybeUndefined::Null => matches!(x, None), MaybeUndefined::Undefined => false, } } /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function /// to the contained nullable value #[inline] pub fn map) -> Option>(self, f: F) -> MaybeUndefined { match self { MaybeUndefined::Value(v) => match f(Some(v)) { Some(v) => MaybeUndefined::Value(v), None => MaybeUndefined::Null, }, MaybeUndefined::Null => match f(None) { Some(v) => MaybeUndefined::Value(v), None => MaybeUndefined::Null, }, MaybeUndefined::Undefined => MaybeUndefined::Undefined, } } /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function /// to the contained value #[inline] pub fn map_value U>(self, f: F) -> MaybeUndefined { match self { MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)), MaybeUndefined::Null => MaybeUndefined::Null, MaybeUndefined::Undefined => MaybeUndefined::Undefined, } } /// Update `value` if the `MaybeUndefined` is not undefined. /// /// # Example /// /// ```rust /// use async_graphql::MaybeUndefined; /// /// let mut value = None; /// /// MaybeUndefined::Value(10i32).update_to(&mut value); /// assert_eq!(value, Some(10)); /// /// MaybeUndefined::Undefined.update_to(&mut value); /// assert_eq!(value, Some(10)); /// /// MaybeUndefined::Null.update_to(&mut value); /// assert_eq!(value, None); /// ``` pub fn update_to(self, value: &mut Option) { match self { MaybeUndefined::Value(new) => *value = Some(new), MaybeUndefined::Null => *value = None, MaybeUndefined::Undefined => {} }; } } impl InputType for MaybeUndefined { type RawValueType = T::RawValueType; fn type_name() -> Cow<'static, str> { T::type_name() } fn qualified_type_name() -> String { T::type_name().to_string() } fn create_type_info(registry: &mut registry::Registry) -> String { T::create_type_info(registry); T::type_name().to_string() } fn parse(value: Option) -> InputValueResult { match value { None => Ok(MaybeUndefined::Undefined), Some(Value::Null) => Ok(MaybeUndefined::Null), Some(value) => Ok(MaybeUndefined::Value( T::parse(Some(value)).map_err(InputValueError::propagate)?, )), } } fn to_value(&self) -> Value { match self { MaybeUndefined::Value(value) => value.to_value(), _ => Value::Null, } } fn as_raw_value(&self) -> Option<&Self::RawValueType> { if let MaybeUndefined::Value(value) = self { value.as_raw_value() } else { None } } } impl MaybeUndefined> { /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a /// `MaybeUndefined`. /// /// [`MaybeUndefined::Undefined`] will be mapped to /// [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. [`MaybeUndefined::Null`] /// will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`. /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and /// [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`. #[inline] pub fn transpose(self) -> Result, E> { match self { MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined), MaybeUndefined::Null => Ok(MaybeUndefined::Null), MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)), MaybeUndefined::Value(Err(e)) => Err(e), } } } impl Serialize for MaybeUndefined { fn serialize(&self, serializer: S) -> Result { match self { MaybeUndefined::Value(value) => value.serialize(serializer), _ => serializer.serialize_none(), } } } impl<'de, T> Deserialize<'de> for MaybeUndefined where T: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { Option::::deserialize(deserializer).map(|value| match value { Some(value) => MaybeUndefined::Value(value), None => MaybeUndefined::Null, }) } } impl From> for Option> { fn from(maybe_undefined: MaybeUndefined) -> Self { match maybe_undefined { MaybeUndefined::Undefined => None, MaybeUndefined::Null => Some(None), MaybeUndefined::Value(value) => Some(Some(value)), } } } impl From>> for MaybeUndefined { fn from(value: Option>) -> Self { match value { Some(Some(value)) => Self::Value(value), Some(None) => Self::Null, None => Self::Undefined, } } } #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; use crate::*; #[test] fn test_maybe_undefined_type() { assert_eq!(MaybeUndefined::::type_name(), "Int"); assert_eq!(MaybeUndefined::::qualified_type_name(), "Int"); assert_eq!(&MaybeUndefined::::type_name(), "Int"); assert_eq!(&MaybeUndefined::::qualified_type_name(), "Int"); } #[test] fn test_maybe_undefined_serde() { assert_eq!( to_value(&MaybeUndefined::Value(100i32)).unwrap(), value!(100) ); assert_eq!( from_value::>(value!(100)).unwrap(), MaybeUndefined::Value(100) ); assert_eq!( from_value::>(value!(null)).unwrap(), MaybeUndefined::Null ); #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] struct A { a: MaybeUndefined, } assert_eq!( to_value(&A { a: MaybeUndefined::Value(100i32) }) .unwrap(), value!({"a": 100}) ); assert_eq!( to_value(&A { a: MaybeUndefined::Null, }) .unwrap(), value!({ "a": null }) ); assert_eq!( to_value(&A { a: MaybeUndefined::Undefined, }) .unwrap(), value!({ "a": null }) ); assert_eq!( from_value::(value!({"a": 100})).unwrap(), A { a: MaybeUndefined::Value(100i32) } ); assert_eq!( from_value::(value!({ "a": null })).unwrap(), A { a: MaybeUndefined::Null } ); assert_eq!( from_value::(value!({})).unwrap(), A { a: MaybeUndefined::Null } ); } #[test] fn test_maybe_undefined_to_nested_option() { assert_eq!(Option::>::from(MaybeUndefined::Undefined), None); assert_eq!( Option::>::from(MaybeUndefined::Null), Some(None) ); assert_eq!( Option::>::from(MaybeUndefined::Value(42)), Some(Some(42)) ); } #[test] fn test_as_opt_ref() { let value = MaybeUndefined::::Undefined; let r = value.as_opt_ref(); assert_eq!(r, None); let value = MaybeUndefined::::Null; let r = value.as_opt_ref(); assert_eq!(r, Some(None)); let value = MaybeUndefined::::Value("abc".to_string()); let r = value.as_opt_ref(); assert_eq!(r, Some(Some(&"abc".to_string()))); } #[test] fn test_as_opt_deref() { let value = MaybeUndefined::::Undefined; let r = value.as_opt_deref(); assert_eq!(r, None); let value = MaybeUndefined::::Null; let r = value.as_opt_deref(); assert_eq!(r, Some(None)); let value = MaybeUndefined::::Value("abc".to_string()); let r = value.as_opt_deref(); assert_eq!(r, Some(Some("abc"))); } #[test] fn test_contains_value() { let test = "abc"; let mut value: MaybeUndefined = MaybeUndefined::Undefined; assert!(!value.contains_value(&test)); value = MaybeUndefined::Null; assert!(!value.contains_value(&test)); value = MaybeUndefined::Value("abc".to_string()); assert!(value.contains_value(&test)); } #[test] fn test_contains() { let test = Some("abc"); let none: Option<&str> = None; let mut value: MaybeUndefined = MaybeUndefined::Undefined; assert!(!value.contains(&test)); assert!(!value.contains(&none)); value = MaybeUndefined::Null; assert!(!value.contains(&test)); assert!(value.contains(&none)); value = MaybeUndefined::Value("abc".to_string()); assert!(value.contains(&test)); assert!(!value.contains(&none)); } #[test] fn test_map_value() { let mut value: MaybeUndefined = MaybeUndefined::Undefined; assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined); value = MaybeUndefined::Null; assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null); value = MaybeUndefined::Value(5); assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true)); } #[test] fn test_map() { let mut value: MaybeUndefined = MaybeUndefined::Undefined; assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined); value = MaybeUndefined::Null; assert_eq!( value.map(|v| Some(v.is_some())), MaybeUndefined::Value(false) ); value = MaybeUndefined::Value(5); assert_eq!( value.map(|v| Some(v.is_some())), MaybeUndefined::Value(true) ); } #[test] fn test_transpose() { let mut value: MaybeUndefined> = MaybeUndefined::Undefined; assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined)); value = MaybeUndefined::Null; assert_eq!(value.transpose(), Ok(MaybeUndefined::Null)); value = MaybeUndefined::Value(Ok(5)); assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5))); value = MaybeUndefined::Value(Err("error")); assert_eq!(value.transpose(), Err("error")); } }