-
Notifications
You must be signed in to change notification settings - Fork 58
feat: implement tristate optionals (some, null, undefined) #1149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -193,7 +193,7 @@ fn input_type_spec_imp( | |
| needs_owned, | ||
| is_subobject_with_lifetime, | ||
| ) | ||
| .map(|type_spec| format!("Option<{type_spec}>",)); | ||
| .map(|type_spec| format!("cynic::MaybeUndefined<{type_spec}>",)); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to force every user of the generator into using MaybeUndefined. I'd rather this was controlled by a parameter somewhere - either a parameter passed in rust or maybe a directive in the query that we're generating from? |
||
| } | ||
|
|
||
| match ty { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| //! certain changes to be made in a backwards compatible way, this module provides | ||
| //! some traits and macros to help enforce those. | ||
|
|
||
| use crate::Id; | ||
| use crate::{Id, MaybeUndefined}; | ||
|
|
||
| /// Determines whether a type can be coerced into a given schema type. | ||
| /// | ||
|
|
@@ -13,6 +13,12 @@ use crate::Id; | |
| pub trait CoercesTo<T> {} | ||
|
|
||
| impl<T, TypeLock> CoercesTo<Option<TypeLock>> for Option<T> where T: CoercesTo<TypeLock> {} | ||
| impl<T, TypeLock> CoercesTo<Option<TypeLock>> for MaybeUndefined<T> where T: CoercesTo<TypeLock> {} | ||
| impl<T, TypeLock> CoercesTo<MaybeUndefined<TypeLock>> for Option<T> where T: CoercesTo<TypeLock> {} | ||
| impl<T, TypeLock> CoercesTo<MaybeUndefined<TypeLock>> for MaybeUndefined<T> where | ||
| T: CoercesTo<TypeLock> | ||
| { | ||
| } | ||
|
Comment on lines
+17
to
+21
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure these |
||
| impl<T, TypeLock> CoercesTo<Vec<TypeLock>> for Vec<T> where T: CoercesTo<TypeLock> {} | ||
| impl<T, TypeLock> CoercesTo<Vec<TypeLock>> for [T] where T: CoercesTo<TypeLock> {} | ||
|
|
||
|
|
@@ -27,10 +33,14 @@ macro_rules! impl_coercions { | |
| ($target:ty [$($impl_generics: tt)*] [$($where_clause: tt)*], $typelock:ty) => { | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<$typelock> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Option<$typelock>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<$crate::MaybeUndefined<$typelock>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Vec<$typelock>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Option<Vec<$typelock>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Option<Vec<Option<$typelock>>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Option<Option<$typelock>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<$crate::MaybeUndefined<Vec<$typelock>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<$crate::MaybeUndefined<Vec<$crate::MaybeUndefined<$typelock>>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<$crate::MaybeUndefined<$crate::MaybeUndefined<$typelock>>> for $target $($where_clause)* {} | ||
| impl $($impl_generics)* $crate::coercions::CoercesTo<Vec<Vec<$typelock>>> for $target $($where_clause)* {} | ||
| }; | ||
| } | ||
|
|
@@ -63,17 +73,26 @@ mod tests { | |
| fn test_coercions() { | ||
| assert_impl_all!(i32: CoercesTo<i32>); | ||
| assert_impl_all!(i32: CoercesTo<Option<i32>>); | ||
| assert_impl_all!(i32: CoercesTo<MaybeUndefined<i32>>); | ||
| assert_impl_all!(i32: CoercesTo<Vec<i32>>); | ||
| assert_impl_all!(i32: CoercesTo<Option<Vec<i32>>>); | ||
| assert_impl_all!(i32: CoercesTo<MaybeUndefined<Vec<i32>>>); | ||
| assert_impl_all!(i32: CoercesTo<Vec<Vec<i32>>>); | ||
|
|
||
| assert_impl_all!(Option<i32>: CoercesTo<Option<i32>>); | ||
| assert_impl_all!(Option<i32>: CoercesTo<Option<Option<i32>>>); | ||
|
|
||
| assert_impl_all!(MaybeUndefined<i32>: CoercesTo<MaybeUndefined<i32>>); | ||
| assert_impl_all!(MaybeUndefined<i32>: CoercesTo<MaybeUndefined<Option<i32>>>); | ||
|
|
||
| assert_impl_all!(MaybeUndefined<i32>: CoercesTo<Option<i32>>); | ||
| assert_impl_all!(Option<i32>: CoercesTo<MaybeUndefined<i32>>); | ||
|
|
||
| assert_impl_all!(Vec<i32>: CoercesTo<Vec<i32>>); | ||
| assert_impl_all!(Vec<i32>: CoercesTo<Vec<Vec<i32>>>); | ||
|
|
||
| assert_not_impl_any!(Vec<i32>: CoercesTo<i32>); | ||
| assert_not_impl_any!(Option<i32>: CoercesTo<i32>); | ||
| assert_not_impl_any!(MaybeUndefined<i32>: CoercesTo<i32>); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| use std::borrow::Cow; | ||
|
|
||
| use crate::{queries::SelectionBuilder, QueryVariablesFields}; | ||
| use crate::{queries::SelectionBuilder, MaybeUndefined, QueryVariablesFields}; | ||
|
|
||
| /// A trait that marks a type as part of a GraphQL query. | ||
| /// | ||
|
|
@@ -37,6 +37,18 @@ where | |
| } | ||
| } | ||
|
|
||
| impl<T> QueryFragment for MaybeUndefined<T> | ||
| where | ||
| T: QueryFragment, | ||
| { | ||
| type SchemaType = Option<T::SchemaType>; | ||
| type VariablesFields = T::VariablesFields; | ||
|
|
||
| fn query(builder: SelectionBuilder<'_, Self::SchemaType, Self::VariablesFields>) { | ||
| T::query(builder.into_inner()) | ||
| } | ||
| } | ||
|
Comment on lines
+40
to
+50
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not really sure is there's a use case for MaybeUndefined as a QueryFragment. It makes sense for input types but not so much for output types which will never be undefined in a well formed graphql response.... |
||
|
|
||
| impl<T> QueryFragment for Vec<T> | ||
| where | ||
| T: QueryFragment, | ||
|
|
@@ -163,6 +175,13 @@ where | |
| type SchemaType = Option<T::SchemaType>; | ||
| } | ||
|
|
||
| impl<T> InputObject for MaybeUndefined<T> | ||
| where | ||
| T: InputObject, | ||
| { | ||
| type SchemaType = Option<T::SchemaType>; | ||
| } | ||
|
|
||
| impl<T> InputObject for Vec<T> | ||
| where | ||
| T: InputObject, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| use serde::{Deserialize, Serialize}; | ||
|
|
||
| use std::ops::{Deref, DerefMut}; | ||
|
|
||
| /// A wrapper around async-graphql's [`MaybeUndefined`](https://docs.rs/async-graphql/latest/async_graphql/types/enum.MaybeUndefined.html). | ||
| /// | ||
| /// You can initialize it from: | ||
| /// - `From<Option<T>>` will become T or null. | ||
| /// - `Default` will become undefined. | ||
| #[derive(Serialize, Deserialize, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] | ||
| pub struct MaybeUndefined<T>(async_graphql::MaybeUndefined<T>); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I already mentioned can you implement MaybeUndefined here rather than pulling in async_graphql |
||
|
|
||
| impl<T> MaybeUndefined<T> { | ||
| fn inner(&self) -> &async_graphql::MaybeUndefined<T> { | ||
| &self.0 | ||
| } | ||
|
|
||
| fn inner_mut(&mut self) -> &mut async_graphql::MaybeUndefined<T> { | ||
| &mut self.0 | ||
| } | ||
|
|
||
| /// Returns true if the `MaybeUndefined<T>` is undefined. | ||
| /// | ||
| /// Deserialization ought to be skipped for this when used as a field. | ||
| pub fn is_undefined(&self) -> bool { | ||
| self.0.is_undefined() | ||
| } | ||
| } | ||
|
|
||
| impl<T> From<async_graphql::MaybeUndefined<T>> for MaybeUndefined<T> { | ||
| fn from(value: async_graphql::MaybeUndefined<T>) -> Self { | ||
| Self(value) | ||
| } | ||
| } | ||
|
|
||
| impl<T> Deref for MaybeUndefined<T> { | ||
| type Target = async_graphql::MaybeUndefined<T>; | ||
|
|
||
| fn deref(&self) -> &Self::Target { | ||
| self.inner() | ||
| } | ||
| } | ||
|
|
||
| impl<T> DerefMut for MaybeUndefined<T> { | ||
| fn deref_mut(&mut self) -> &mut Self::Target { | ||
| self.inner_mut() | ||
| } | ||
| } | ||
|
|
||
| impl<T1, T2> From<Option<T1>> for MaybeUndefined<T2> | ||
| where | ||
| T2: From<T1>, | ||
| { | ||
| fn from(value: Option<T1>) -> Self { | ||
| Self(match value { | ||
| Some(value) => async_graphql::MaybeUndefined::Value(T2::from(value)), | ||
| None => async_graphql::MaybeUndefined::Null, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl<T> Default for MaybeUndefined<T> { | ||
| fn default() -> Self { | ||
| Self(async_graphql::MaybeUndefined::Undefined) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test() { | ||
| assert_eq!( | ||
| MaybeUndefined::from(None::<bool>), | ||
| MaybeUndefined(async_graphql::MaybeUndefined::<bool>::Null) | ||
| ); | ||
| assert_eq!( | ||
| MaybeUndefined::from(Some(true)), | ||
| MaybeUndefined(async_graphql::MaybeUndefined::Value(true)) | ||
| ); | ||
| assert_eq!( | ||
| MaybeUndefined::<bool>::default(), | ||
| MaybeUndefined(async_graphql::MaybeUndefined::Undefined) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,31 @@ | ||
| // use crate::MaybeUndefined; | ||
|
|
||
| /// Encodes the rules for what types can be flattened into other types | ||
| /// via the `#[cynic(flatten)]` attribute. | ||
| /// | ||
| /// This trait is sealed and can't be implemented by users of cynic. | ||
| pub trait FlattensInto<T>: private::Sealed<T> {} | ||
|
|
||
| impl<T> FlattensInto<Vec<T>> for Vec<Option<T>> {} | ||
|
|
||
| impl<T> FlattensInto<Vec<T>> for Option<Vec<T>> {} | ||
| impl<T> FlattensInto<Option<Vec<T>>> for Option<Vec<Option<T>>> {} | ||
| impl<T> FlattensInto<Vec<T>> for Option<Vec<Option<T>>> {} | ||
|
|
||
| // impl<T> FlattensInto<Vec<T>> for MaybeUndefined<Vec<T>> {} | ||
| // impl<T> FlattensInto<Option<Vec<T>>> for MaybeUndefined<Vec<Option<T>>> {} | ||
| // impl<T> FlattensInto<Vec<T>> for MaybeUndefined<Vec<Option<T>>> {} | ||
|
|
||
| mod private { | ||
| pub trait Sealed<T> {} | ||
|
|
||
| impl<T> Sealed<Vec<T>> for Vec<Option<T>> {} | ||
|
|
||
| impl<T> Sealed<Vec<T>> for Option<Vec<T>> {} | ||
| impl<T> Sealed<Option<Vec<T>>> for Option<Vec<Option<T>>> {} | ||
| impl<T> Sealed<Vec<T>> for Option<Vec<Option<T>>> {} | ||
|
|
||
| // impl<T> Sealed<Vec<T>> for MaybeUndefined<Vec<T>> {} | ||
| // impl<T> Sealed<Option<Vec<T>>> for MaybeUndefined<Vec<Option<T>>> {} | ||
| // impl<T> Sealed<Vec<T>> for MaybeUndefined<Vec<Option<T>>> {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,8 @@ | |
| //! usually be marker types and the associated types will also usually be | ||
| //! markers. | ||
|
|
||
| use crate::MaybeUndefined; | ||
|
|
||
| /// Indicates that a struct represents a Field in a graphql schema. | ||
| pub trait Field { | ||
| /// The schema marker type of this field. | ||
|
|
@@ -74,6 +76,20 @@ where | |
| type SchemaType = Option<U::SchemaType>; | ||
| } | ||
|
|
||
| impl<T, U> IsScalar<MaybeUndefined<T>> for MaybeUndefined<U> | ||
| where | ||
| U: IsScalar<T>, | ||
| { | ||
| type SchemaType = MaybeUndefined<U::SchemaType>; | ||
| } | ||
|
Comment on lines
+79
to
+84
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty sure this impl isn't needed |
||
|
|
||
| impl<T, U> IsScalar<Option<T>> for MaybeUndefined<U> | ||
| where | ||
| U: IsScalar<T>, | ||
| { | ||
| type SchemaType = Option<U::SchemaType>; | ||
| } | ||
|
|
||
| impl<T, U> IsScalar<Vec<T>> for Vec<U> | ||
| where | ||
| U: IsScalar<T>, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async-graphql is a very heavy dependency to pull in, particularly just for a single 3 variant enum. Can you implement this without the dependency please.