diff --git a/cynic-codegen/src/fragment_derive/deserialize_impl.rs b/cynic-codegen/src/fragment_derive/deserialize_impl.rs index 486726fe2..fe4f12c34 100644 --- a/cynic-codegen/src/fragment_derive/deserialize_impl.rs +++ b/cynic-codegen/src/fragment_derive/deserialize_impl.rs @@ -1,6 +1,10 @@ use proc_macro2::TokenStream; use quote::quote_spanned; +use crate::schema::{types::OutputType, Schema, Unvalidated}; + +use super::FieldKind; + use { super::FragmentDeriveField, crate::{generics_for_serde, schema::types as schema}, @@ -28,6 +32,8 @@ struct Field { ty: syn::Type, field_variant_name: proc_macro2::Ident, serialized_name: Option, + inner_kind: Option, + field_marker: syn::Path, is_spread: bool, is_flattened: bool, is_recurse: bool, @@ -36,16 +42,20 @@ struct Field { impl<'a> DeserializeImpl<'a> { pub fn new( + schema: &Schema<'_, Unvalidated>, fields: &[(FragmentDeriveField, Option>)], name: &'a syn::Ident, generics: &'a syn::Generics, + field_module_path: &syn::Path, ) -> Self { let spreading = fields.iter().any(|f| *f.0.spread); let target_struct = name; let fields = fields .iter() - .map(|(field, schema_field)| process_field(field, schema_field.as_ref())) + .map(|(field, schema_field)| { + process_field(schema, field, schema_field.as_ref(), field_module_path) + }) .collect(); match spreading { @@ -96,15 +106,23 @@ impl quote::ToTokens for StandardDeserializeImpl<'_> { let field_names = self.fields.iter().map(|f| &f.rust_name).collect::>(); let field_decodes = self.fields.iter().map(|f| { let field_name = &f.rust_name; + let field_marker = &f.field_marker; let ty = &f.ty; + let mut ty = quote! { #ty }; + let mut trailer = quote! {}; + + if matches!(f.inner_kind, Some(FieldKind::Scalar)) { + ty = quote! { cynic::__private::ScalarDeserialize<#ty, <#field_marker as cynic::schema::Field>::Type> }; + trailer.append_all(quote! { .into_inner() }); + } + if f.is_flattened { - quote! { - #field_name = Some(__map.next_value::>()?.into_inner()); - } - } else { - quote! { - #field_name = Some(__map.next_value()?); - } + ty = quote! { cynic::__private::Flattened<#ty> }; + trailer.append_all(quote! { .into_inner() }); + } + + quote! { + #field_name = Some(__map.next_value::<#ty>()? #trailer); } }); @@ -267,10 +285,22 @@ impl quote::ToTokens for SpreadingDeserializeImpl<'_> { } } -fn process_field(field: &FragmentDeriveField, schema_field: Option<&schema::Field<'_>>) -> Field { +fn process_field( + schema: &Schema<'_, Unvalidated>, + field: &FragmentDeriveField, + schema_field: Option<&schema::Field<'_>>, + field_module_path: &syn::Path, +) -> Field { // Should be ok to unwrap since we only accept struct style input let rust_name = field.ident.as_ref().unwrap(); let field_variant_name = rust_name.clone(); + let schema_type = schema_field.map(|field| field.field_type.inner_type(schema)); + let inner_kind = schema_type.as_ref().map(|ty| ty.as_kind()); + + let field_marker = match schema_field { + Some(field) => field.marker_ident().to_path(field_module_path), + None => syn::parse_quote!(String), + }; Field { field_variant_name, @@ -283,5 +313,7 @@ fn process_field(field: &FragmentDeriveField, schema_field: Option<&schema::Fiel is_flattened: *field.flatten, is_recurse: field.recurse.is_some(), is_feature_flagged: field.feature.is_some(), + inner_kind, + field_marker, } } diff --git a/cynic-codegen/src/fragment_derive/fragment_impl.rs b/cynic-codegen/src/fragment_derive/fragment_impl.rs index ad399e43f..694f474be 100644 --- a/cynic-codegen/src/fragment_derive/fragment_impl.rs +++ b/cynic-codegen/src/fragment_derive/fragment_impl.rs @@ -17,6 +17,7 @@ use crate::{ use super::{ arguments::{arguments_from_field_attrs, process_arguments}, fragment_derive_type::FragmentDeriveType, + FieldKind, }; use super::input::FragmentDeriveField; @@ -54,14 +55,6 @@ struct SpreadSelection { span: proc_macro2::Span, } -enum FieldKind { - Composite, - Scalar, - Enum, - Interface, - Union, -} - impl<'schema, 'a: 'schema> FragmentImpl<'schema, 'a> { #[allow(clippy::too_many_arguments)] pub fn new_for( @@ -263,10 +256,10 @@ impl quote::ToTokens for FieldSelection<'_> { } } FieldKind::Scalar if self.flatten => quote_spanned! { self.span => - <#aligned_type as cynic::schema::IsScalar<#aligned_type>>::SchemaType + <#aligned_type as cynic::schema::IsOutputScalar<#aligned_type>>::SchemaType }, FieldKind::Scalar => quote_spanned! { self.span => - <#aligned_type as cynic::schema::IsScalar< + <#aligned_type as cynic::schema::IsOutputScalar< <#field_marker_type_path as cynic::schema::Field>::Type >>::SchemaType }, @@ -383,13 +376,10 @@ impl quote::ToTokens for SpreadSelection { } impl OutputType<'_> { - fn as_kind(&self) -> FieldKind { - match self { - OutputType::Scalar(_) => FieldKind::Scalar, - OutputType::Enum(_) => FieldKind::Enum, - OutputType::Object(_) => FieldKind::Composite, - OutputType::Interface(_) => FieldKind::Interface, - OutputType::Union(_) => FieldKind::Union, - } + fn is_composite(&self) -> bool { + matches!( + self, + OutputType::Object(_) | OutputType::Interface(_) | OutputType::Union(_) + ) } } diff --git a/cynic-codegen/src/fragment_derive/mod.rs b/cynic-codegen/src/fragment_derive/mod.rs index 3de64bdba..a13b3425f 100644 --- a/cynic-codegen/src/fragment_derive/mod.rs +++ b/cynic-codegen/src/fragment_derive/mod.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use crate::{ schema::{ - types::{self as schema}, + types::{self as schema, OutputType}, Schema, }, suggestions::FieldSuggestionError, @@ -63,7 +63,15 @@ pub fn fragment_derive_impl(input: FragmentDeriveInput) -> Result( Err(errors) } +impl OutputType<'_> { + fn as_kind(&self) -> FieldKind { + match self { + OutputType::Scalar(_) => FieldKind::Scalar, + OutputType::Enum(_) => FieldKind::Enum, + OutputType::Object(_) => FieldKind::Composite, + OutputType::Interface(_) => FieldKind::Interface, + OutputType::Union(_) => FieldKind::Union, + } + } +} + +#[derive(Clone, Copy)] +enum FieldKind { + Composite, + Scalar, + Enum, + Interface, + Union, +} + #[cfg(test)] mod tests; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_and_rename.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_and_rename.snap index 8fcb7a668..e153cb3d9 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_and_rename.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_and_rename.snap @@ -63,13 +63,13 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { if post.is_some() { return Err(cynic::serde::de::Error::duplicate_field("post")); } - post = Some(__map.next_value()?); + post = Some(__map.next_value::>()?); } __FragmentDeriveField::posts => { if posts.is_some() { return Err(cynic::serde::de::Error::duplicate_field("allPosts")); } - posts = Some(__map.next_value()?); + posts = Some(__map.next_value::>()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_literals.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_literals.snap index 3ee4d9498..362f26d61 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_literals.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__argument_literals.snap @@ -82,7 +82,7 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { "filteredPosts", )); } - filteredPosts = Some(__map.next_value()?); + filteredPosts = Some(__map.next_value::>()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__feature_flagging.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__feature_flagging.snap index 07ead44b7..8c4f303de 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__feature_flagging.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__feature_flagging.snap @@ -11,10 +11,7 @@ impl cynic::QueryFragment for MyQuery { mut builder: cynic::queries::SelectionBuilder<'_, Self::SchemaType, Self::VariablesFields>, ) { #![allow(unused_mut)] - let mut field_builder = builder - .select_field::::Type, - >>::SchemaType>(); + let mut field_builder = builder . select_field :: < schema :: __fields :: Query :: __typename , < String as cynic :: schema :: IsOutputScalar < < schema :: __fields :: Query :: __typename as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; if builder.is_feature_enabled("2018") { let mut field_builder = builder . select_field :: < schema :: __fields :: Query :: filteredPosts , < Vec < BlogPostOutput > as cynic :: QueryFragment > :: SchemaType > () ; as cynic::QueryFragment>::query(field_builder.select_children()); @@ -62,7 +59,7 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { if __typename.is_some() { return Err(cynic::serde::de::Error::duplicate_field("__typename")); } - __typename = Some(__map.next_value()?); + __typename = Some (__map . next_value :: < cynic :: __private :: ScalarDeserialize < String , < schema :: __fields :: Query :: __typename as cynic :: schema :: Field > :: Type > > () ? . into_inner ()) ; } __FragmentDeriveField::filteredPosts => { if filteredPosts.is_some() { @@ -70,7 +67,7 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { "filteredPosts", )); } - filteredPosts = Some(__map.next_value()?); + filteredPosts = Some(__map.next_value::>()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__flatten_attr.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__flatten_attr.snap index cf068afbc..0bfada3d7 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__flatten_attr.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__flatten_attr.snap @@ -11,7 +11,7 @@ impl cynic::QueryFragment for Film { mut builder: cynic::queries::SelectionBuilder<'_, Self::SchemaType, Self::VariablesFields>, ) { #![allow(unused_mut)] - let mut field_builder = builder . select_flattened_field :: < schema :: __fields :: Film :: producers , < Vec < String > as cynic :: schema :: IsScalar < Vec < String > >> :: SchemaType , < schema :: __fields :: Film :: producers as cynic :: schema :: Field > :: Type , > () ; + let mut field_builder = builder . select_flattened_field :: < schema :: __fields :: Film :: producers , < Vec < String > as cynic :: schema :: IsOutputScalar < Vec < String > >> :: SchemaType , < schema :: __fields :: Film :: producers as cynic :: schema :: Field > :: Type , > () ; } fn name() -> Option> { Some(std::borrow::Cow::Borrowed("Film")) @@ -52,11 +52,7 @@ impl<'de> cynic::serde::Deserialize<'de> for Film { if producers.is_some() { return Err(cynic::serde::de::Error::duplicate_field("producers")); } - producers = Some( - __map - .next_value::>>()? - .into_inner(), - ); + producers = Some (__map . next_value :: < cynic :: __private :: Flattened < cynic :: __private :: ScalarDeserialize < Vec < String > , < schema :: __fields :: Film :: producers as cynic :: schema :: Field > :: Type > > > () ? . into_inner () . into_inner ()) ; } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__not_sure.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__not_sure.snap index 0de7906ff..38612dffe 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__not_sure.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__not_sure.snap @@ -92,7 +92,7 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { "filteredPosts", )); } - filteredPosts = Some(__map.next_value()?); + filteredPosts = Some(__map.next_value::>()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__simple_struct.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__simple_struct.snap index 2446966d7..50c33d808 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__simple_struct.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__simple_struct.snap @@ -11,7 +11,7 @@ impl cynic::QueryFragment for BlogPostOutput { mut builder: cynic::queries::SelectionBuilder<'_, Self::SchemaType, Self::VariablesFields>, ) { #![allow(unused_mut)] - let mut field_builder = builder . select_field :: < schema :: __fields :: BlogPost :: hasMetadata , < Option < bool > as cynic :: schema :: IsScalar < < schema :: __fields :: BlogPost :: hasMetadata as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; + let mut field_builder = builder . select_field :: < schema :: __fields :: BlogPost :: hasMetadata , < Option < bool > as cynic :: schema :: IsOutputScalar < < schema :: __fields :: BlogPost :: hasMetadata as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; let mut field_builder = builder . select_field :: < schema :: __fields :: BlogPost :: author , < AuthorOutput as cynic :: QueryFragment > :: SchemaType > () ; ::query(field_builder.select_children()); } @@ -59,13 +59,13 @@ impl<'de> cynic::serde::Deserialize<'de> for BlogPostOutput { "hasMetadata", )); } - has_metadata = Some(__map.next_value()?); + has_metadata = Some (__map . next_value :: < cynic :: __private :: ScalarDeserialize < Option < bool > , < schema :: __fields :: BlogPost :: hasMetadata as cynic :: schema :: Field > :: Type > > () ? . into_inner ()) ; } __FragmentDeriveField::author => { if author.is_some() { return Err(cynic::serde::de::Error::duplicate_field("author")); } - author = Some(__map.next_value()?); + author = Some(__map.next_value::()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field1.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field1.snap index 861656a8d..a156e497e 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field1.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field1.snap @@ -11,7 +11,7 @@ impl cynic::QueryFragment for Film { mut builder: cynic::queries::SelectionBuilder<'_, Self::SchemaType, Self::VariablesFields>, ) { #![allow(unused_mut)] - let mut field_builder = builder . select_field :: < schema :: __fields :: Film :: releaseDate , < Option < String > as cynic :: schema :: IsScalar < < schema :: __fields :: Film :: releaseDate as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; + let mut field_builder = builder . select_field :: < schema :: __fields :: Film :: releaseDate , < Option < String > as cynic :: schema :: IsOutputScalar < < schema :: __fields :: Film :: releaseDate as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; ::query( builder .inline_fragment() diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field2.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field2.snap index 8e9b6ae9a..61365da0b 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field2.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__spread_attr_multi_field2.snap @@ -16,7 +16,7 @@ impl cynic::QueryFragment for Film { .inline_fragment() .select_children::<::VariablesFields>(), ); - let mut field_builder = builder . select_field :: < schema :: __fields :: Film :: releaseDate , < Option < String > as cynic :: schema :: IsScalar < < schema :: __fields :: Film :: releaseDate as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; + let mut field_builder = builder . select_field :: < schema :: __fields :: Film :: releaseDate , < Option < String > as cynic :: schema :: IsOutputScalar < < schema :: __fields :: Film :: releaseDate as cynic :: schema :: Field > :: Type >> :: SchemaType > () ; } fn name() -> Option> { Some(std::borrow::Cow::Borrowed("Film")) diff --git a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__variable_in_argument.snap b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__variable_in_argument.snap index 23a7ed27a..23090811b 100644 --- a/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__variable_in_argument.snap +++ b/cynic-codegen/src/fragment_derive/snapshots/cynic_codegen__fragment_derive__tests__variable_in_argument.snap @@ -60,7 +60,7 @@ impl<'de> cynic::serde::Deserialize<'de> for MyQuery { "filteredPosts", )); } - filteredPosts = Some(__map.next_value()?); + filteredPosts = Some(__map.next_value::>()?); } __FragmentDeriveField::__Other => { __map.next_value::()?; diff --git a/cynic-codegen/src/scalar_derive/mod.rs b/cynic-codegen/src/scalar_derive/mod.rs index 68cbc1f0d..4455ae0e1 100644 --- a/cynic-codegen/src/scalar_derive/mod.rs +++ b/cynic-codegen/src/scalar_derive/mod.rs @@ -84,6 +84,25 @@ pub fn scalar_derive_impl(input: ScalarDeriveInput) -> Result for #ident #ty_generics #where_clause { type SchemaType = #marker_ident; + + fn serialize(&self, serializer: S) -> Result + where + S: cynic::serde::Serializer + { + cynic::serde::Serialize::serialize(self, serializer) + } + } + + #[automatically_derived] + impl #impl_generics cynic::schema::IsOutputScalar<#marker_ident> for #ident #ty_generics #where_clause { + type SchemaType = #marker_ident; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: cynic::serde::Deserializer<'de> + { + ::deserialize(deserializer) + } } #[automatically_derived] diff --git a/cynic-codegen/src/scalar_derive/snapshots/cynic_codegen__scalar_derive__tests__snapshot_scalar_derive.snap b/cynic-codegen/src/scalar_derive/snapshots/cynic_codegen__scalar_derive__tests__snapshot_scalar_derive.snap index 5fd32fb8f..e27024c30 100644 --- a/cynic-codegen/src/scalar_derive/snapshots/cynic_codegen__scalar_derive__tests__snapshot_scalar_derive.snap +++ b/cynic-codegen/src/scalar_derive/snapshots/cynic_codegen__scalar_derive__tests__snapshot_scalar_derive.snap @@ -23,6 +23,22 @@ impl<'de> cynic::serde::Deserialize<'de> for DateTime { #[automatically_derived] impl cynic::schema::IsScalar for DateTime { type SchemaType = schema::DateTime; + fn serialize(&self, serializer: S) -> Result + where + S: cynic::serde::Serializer, + { + cynic::serde::Serialize::serialize(self, serializer) + } +} +#[automatically_derived] +impl cynic::schema::IsOutputScalar for DateTime { + type SchemaType = schema::DateTime; + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: cynic::serde::Deserializer<'de>, + { + ::deserialize(deserializer) + } } #[automatically_derived] impl schema::variable::Variable for DateTime { diff --git a/cynic-codegen/src/schema/markers.rs b/cynic-codegen/src/schema/markers.rs index cc0891fba..19f096e40 100644 --- a/cynic-codegen/src/schema/markers.rs +++ b/cynic-codegen/src/schema/markers.rs @@ -195,6 +195,18 @@ marker_ident_for_named!( ScalarType ); +impl<'a> OutputType<'a> { + pub fn marker_ident(&'a self) -> TypeMarkerIdent<'a> { + match self { + OutputType::Scalar(inner) => inner.marker_ident(), + OutputType::Object(inner) => inner.marker_ident(), + OutputType::Interface(inner) => inner.marker_ident(), + OutputType::Union(inner) => inner.marker_ident(), + OutputType::Enum(inner) => inner.marker_ident(), + } + } +} + impl<'a> Field<'a> { pub fn marker_ident(&'a self) -> FieldMarkerIdent<'a> { FieldMarkerIdent { diff --git a/cynic/src/lib.rs b/cynic/src/lib.rs index 86541e381..328d2b876 100644 --- a/cynic/src/lib.rs +++ b/cynic/src/lib.rs @@ -237,6 +237,25 @@ macro_rules! impl_scalar { #[automatically_derived] impl $crate::schema::IsScalar<$schema_module$(::$schema_module_rest)*::$type_lock> for $type { type SchemaType = $schema_module$(::$schema_module_rest)*::$type_lock; + + fn serialize(&self, serializer: S) -> Result + where + S: $crate::serde::Serializer + { + $crate::serde::Serialize::serialize(self, serializer) + } + } + + #[automatically_derived] + impl $crate::schema::IsOutputScalar<$schema_module$(::$schema_module_rest)*::$type_lock> for $type { + type SchemaType = $schema_module$(::$schema_module_rest)*::$type_lock; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: $crate::serde::Deserializer<'de> + { + ::deserialize(deserializer) + } } // We derive a simple CoercesTo here, but since we don't own $type we can't diff --git a/cynic/src/private/flatten_de.rs b/cynic/src/private/flatten_de.rs index 5f84d965b..e7d6e3240 100644 --- a/cynic/src/private/flatten_de.rs +++ b/cynic/src/private/flatten_de.rs @@ -1,5 +1,7 @@ use serde::Deserialize; +use crate::schema::IsOutputScalar; + pub struct Flattened { inner: T, } @@ -39,3 +41,41 @@ where }) } } + +impl<'de, T, U> Deserialize<'de> for Flattened, U>> +where + Option>>: IsOutputScalar, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + super::ScalarDeserialize::>>, U>::deserialize(deserializer).map( + |opt_vec| Flattened { + inner: todo!( + "opt_vec + .map(|vec| vec.into_iter().flatten().collect::>()) + .unwrap_or_default()," + ), + }, + ) + } +} + +impl<'de, T, U> Deserialize<'de> for Flattened>, U>> +where + Option>>: IsOutputScalar, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + super::ScalarDeserialize::>>, U>::deserialize(deserializer).map( + |scalar_deser| Flattened { + inner: scalar_deser.map(|opt_vec| { + opt_vec.map(|vec| vec.into_iter().flatten().collect::>()) + }), + }, + ) + } +} diff --git a/cynic/src/private/mod.rs b/cynic/src/private/mod.rs index 7f8c3217c..6673bd94a 100644 --- a/cynic/src/private/mod.rs +++ b/cynic/src/private/mod.rs @@ -8,8 +8,10 @@ mod cow_str; mod flatten_de; mod inline_fragment_de; mod key_de; +mod scalar_serde; mod spread_de; pub use flatten_de::Flattened; pub use inline_fragment_de::InlineFragmentVisitor; +pub use scalar_serde::{ScalarDeserialize, ScalarSerialize}; pub use spread_de::Spreadable; diff --git a/cynic/src/private/scalar_serde.rs b/cynic/src/private/scalar_serde.rs new file mode 100644 index 000000000..6f1447263 --- /dev/null +++ b/cynic/src/private/scalar_serde.rs @@ -0,0 +1,69 @@ +use std::marker::PhantomData; + +use serde::Deserialize; + +use crate::schema::{IsOutputScalar, IsScalar}; + +pub struct ScalarDeserialize { + inner: T, + phantom: PhantomData U>, +} + +impl ScalarDeserialize { + pub fn into_inner(self) -> T { + self.inner + } + + pub fn map(self, fun: F) -> ScalarDeserialize + where + F: FnOnce(T) -> NewT, + { + ScalarDeserialize { + inner: fun(self.inner), + phantom: PhantomData, + } + } +} + +impl<'de, T, U> Deserialize<'de> for ScalarDeserialize +where + T: IsOutputScalar, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let inner = >::deserialize(deserializer)?; + + Ok(ScalarDeserialize { + inner, + phantom: PhantomData, + }) + } +} + +pub struct ScalarSerialize<'a, T, U> { + inner: &'a T, + phantom: PhantomData ()>, +} + +impl<'a, T, U> ScalarSerialize<'a, T, U> { + pub fn new(inner: &'a T) -> Self { + ScalarSerialize { + inner, + phantom: PhantomData, + } + } +} + +impl serde::Serialize for ScalarSerialize<'_, T, U> +where + T: IsScalar, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + IsScalar::serialize(self.inner, serializer) + } +} diff --git a/cynic/src/schema.rs b/cynic/src/schema.rs index ea49701de..67ac55333 100644 --- a/cynic/src/schema.rs +++ b/cynic/src/schema.rs @@ -11,6 +11,12 @@ //! usually be marker types and the associated types will also usually be //! markers. +use std::borrow::{Borrow, Cow}; + +use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serializer}; + +use crate::__private::{ScalarDeserialize, ScalarSerialize}; + /// Indicates that a struct represents a Field in a graphql schema. pub trait Field { /// The schema marker type of this field. @@ -52,12 +58,30 @@ pub trait HasArgument { const NAME: &'static str; } +// TODO: Think about the names of the scalar traits.... + /// Indicates that a type is a scalar that maps to the given schema scalar. /// /// Note that this type is actually implemented on the users types. pub trait IsScalar { /// The schema marker type this scalar represents. type SchemaType; + + // TODO: serialize should maybe be on an OutputScalar trait + fn serialize(&self, serializer: S) -> Result + where + S: Serializer; +} + +// TODO: serialize should maybe be on an InputScalar trait +// or maybe just ScalarSerialize/ScalarDeserialize? not sure... +pub trait IsOutputScalar: Sized { + /// The schema marker type this scalar represents. + type SchemaType; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>; } impl IsScalar for &U @@ -65,6 +89,13 @@ where U: IsScalar, { type SchemaType = U::SchemaType; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + >::serialize(self, serializer) + } } impl IsScalar> for Option @@ -72,6 +103,33 @@ where U: IsScalar, { type SchemaType = Option; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Some(inner) => inner.serialize(serializer), + None => serializer.serialize_none(), + } + } +} + +impl IsOutputScalar> for Option +where + U: IsOutputScalar, +{ + type SchemaType = Option; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok( + Option::>::deserialize(deserializer)? + .map(ScalarDeserialize::into_inner), + ) + } } impl IsScalar> for Vec @@ -79,6 +137,34 @@ where U: IsScalar, { type SchemaType = Vec; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for item in self { + seq.serialize_element(&ScalarSerialize::new(item))?; + } + seq.end() + } +} + +impl IsOutputScalar> for Vec +where + U: IsOutputScalar, +{ + type SchemaType = Vec; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Vec::>::deserialize(deserializer)? + .into_iter() + .map(ScalarDeserialize::into_inner) + .collect()) + } } impl IsScalar> for [U] @@ -86,6 +172,17 @@ where U: IsScalar, { type SchemaType = Vec; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for item in self { + seq.serialize_element(&ScalarSerialize::new(item))?; + } + seq.end() + } } impl IsScalar> for [U; SIZE] @@ -93,6 +190,17 @@ where U: IsScalar, { type SchemaType = Vec; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for item in self { + seq.serialize_element(&ScalarSerialize::new(item))?; + } + seq.end() + } } impl IsScalar> for Box @@ -100,6 +208,27 @@ where U: IsScalar, { type SchemaType = Box; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_ref().serialize(serializer) + } +} + +impl IsOutputScalar> for Box +where + U: IsOutputScalar, +{ + type SchemaType = Box; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + U::deserialize(deserializer).map(Box::new) + } } impl IsScalar for std::borrow::Cow<'_, U> @@ -107,30 +236,161 @@ where U: IsScalar + ToOwned, { type SchemaType = U::SchemaType; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_ref().serialize(serializer) + } +} + +impl IsOutputScalar for std::borrow::Cow<'_, U> +where + U: IsOutputScalar + ToOwned, +{ + type SchemaType = U::SchemaType; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Cow::Owned(U::deserialize(deserializer)?.to_owned())) + } +} + +impl IsOutputScalar for std::borrow::Cow<'static, str> { + type SchemaType = String; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Cow::Owned( + ::deserialize(deserializer)?.to_owned(), + )) + } } impl IsScalar for bool { type SchemaType = bool; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } +} + +impl IsOutputScalar for bool { + type SchemaType = bool; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer) + } } impl IsScalar for String { type SchemaType = String; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } +} + +impl IsOutputScalar for String { + type SchemaType = String; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer) + } } impl IsScalar for str { type SchemaType = String; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } } impl IsScalar for i32 { type SchemaType = i32; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } +} + +impl IsOutputScalar for i32 { + type SchemaType = i32; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer) + } } impl IsScalar for f64 { type SchemaType = f64; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } +} + +impl IsOutputScalar for f64 { + type SchemaType = f64; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer) + } } impl IsScalar for crate::Id { type SchemaType = crate::Id; + + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde::Serialize::serialize(self, serializer) + } +} + +impl IsOutputScalar for crate::Id { + type SchemaType = crate::Id; + + fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + serde::Deserialize::deserialize(deserializer) + } } /// A marker trait that indicates a particular type is at the root of a GraphQL diff --git a/cynic/tests/mutation_generics.rs b/cynic/tests/mutation_generics.rs index 617bf7807..60f479033 100644 --- a/cynic/tests/mutation_generics.rs +++ b/cynic/tests/mutation_generics.rs @@ -40,9 +40,9 @@ pub struct SignIn { variables = "SignInVariablesMoreGeneric", schema_path = "../schemas/raindancer.graphql" )] -pub struct SignInMoreGeneric> +pub struct SignInMoreGeneric> where - >::SchemaType: cynic::queries::IsFieldType, + >::SchemaType: cynic::queries::IsFieldType, { #[arguments(input: $input)] pub sign_in: SI, diff --git a/cynic/tests/scalars.rs b/cynic/tests/scalars.rs index 4e6e0bf23..475c95bc6 100644 --- a/cynic/tests/scalars.rs +++ b/cynic/tests/scalars.rs @@ -1,3 +1,5 @@ +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + mod schema { cynic::use_schema!("tests/test-schema.graphql"); } @@ -9,23 +11,30 @@ pub struct DateTime(pub chrono::DateTime); // Make sure we can impl_scalar for third party types. cynic::impl_scalar!(chrono::DateTime, schema::DateTime); -#[derive(cynic::Scalar)] -#[cynic(graphql_type = "DateTime")] -pub struct DateTimeInner
(pub DT); +// TODO: Reinstate this... +// #[derive(cynic::Scalar)] +// #[cynic(graphql_type = "DateTime")] +// pub struct DateTimeInner
(pub DT); // Make sure we can use impl scalar on built in types +#[derive(serde::Serialize, serde::Deserialize)] pub struct MyString(String); + cynic::impl_scalar!(MyString, schema::String); +#[derive(serde::Serialize, serde::Deserialize)] pub struct MyInt(i64); cynic::impl_scalar!(MyInt, schema::Int); +#[derive(serde::Serialize, serde::Deserialize)] pub struct MyFloat(f64); cynic::impl_scalar!(MyFloat, schema::Float); +#[derive(serde::Serialize, serde::Deserialize)] pub struct MyBool(bool); cynic::impl_scalar!(MyBool, schema::Boolean); +#[derive(serde::Serialize, serde::Deserialize)] pub struct MyId(cynic::Id); cynic::impl_scalar!(MyId, schema::ID);