Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions cynic-codegen/src/fragment_derive/fragment_impl.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use darling::usage::{GenericsExt, Purpose, UsesTypeParams};
use syn::{parse_quote, Token, WhereClause};

use {
proc_macro2::{Span, TokenStream},
quote::{quote, quote_spanned},
Expand Down Expand Up @@ -28,6 +31,7 @@ pub struct FragmentImpl<'schema, 'a> {
variables_fields: syn::Type,
graphql_type_name: String,
schema_type_path: syn::Path,
additional_where: Option<syn::WhereClause>,
}

#[allow(clippy::large_enum_variant)]
Expand Down Expand Up @@ -83,6 +87,14 @@ impl<'schema, 'a: 'schema> FragmentImpl<'schema, 'a> {
let variables_fields = variables_fields_path(variables);
let variables_fields = variables_fields.as_ref();

let additional_where = additional_where_clause(
generics,
fields,
schema,
&field_module_path,
variables_fields,
);

let selections = fields
.iter()
.map(|(field, schema_field)| {
Expand Down Expand Up @@ -111,6 +123,7 @@ impl<'schema, 'a: 'schema> FragmentImpl<'schema, 'a> {
variables_fields,
graphql_type_name: graphql_type_name.to_string(),
schema_type_path,
additional_where,
})
}
}
Expand Down Expand Up @@ -167,6 +180,69 @@ fn process_field<'a>(
}))
}

fn additional_where_clause(
generics: &syn::Generics,
fields: &[(FragmentDeriveField, Option<Field<'_>>)],
schema: &Schema<'_, Unvalidated>,
field_module_path: &syn::Path,
variables_fields: Option<&syn::Path>,
) -> Option<WhereClause> {
let all_params = generics.declared_type_params();
if all_params.is_empty() {
return None;
}
let options = Purpose::BoundImpl.into();

let mut predicates: Vec<syn::WherePredicate> = vec![];

for (field, schema_field) in fields {
let Some(schema_field) = schema_field else {
continue;
};
let inner_type = schema_field.field_type.inner_type(schema);
if !inner_type.is_composite() {
// We only care about generics on composite types for now
continue;
}
if *field.spread || *field.flatten {
// We could probably support these, but I don't want to figure it out right now.
// Leave it to the user to provide these bounds
continue;
}
if field.ty.uses_type_params(&options, &all_params).is_empty() {
// If this field uses no type params we skip it
continue;
}

let ty = &field.ty;
let marker_ty = schema_field.marker_ident().to_path(field_module_path);
predicates.push(parse_quote! {
#ty: cynic::QueryFragment<SchemaType = <#marker_ty as cynic::schema::Field>::Type>
});
match variables_fields {
Some(variables_fields) => {
predicates.push(parse_quote! {
#variables_fields: cynic::queries::VariableMatch<<#ty as cynic::QueryFragment>::VariablesFields>
});
}
None => {
predicates.push(parse_quote! {
(): cynic::queries::VariableMatch<<#ty as cynic::QueryFragment>::VariablesFields>
});
}
}
}

if predicates.is_empty() {
return None;
}

Some(WhereClause {
where_token: <Token![where]>::default(),
predicates: predicates.into_iter().collect(),
})
}

impl quote::ToTokens for FragmentImpl<'_, '_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
use quote::TokenStreamExt;
Expand All @@ -179,6 +255,17 @@ impl quote::ToTokens for FragmentImpl<'_, '_> {
let fragment_name = proc_macro2::Literal::string(&target_struct.to_string());
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();

let where_clause = match (where_clause, &self.additional_where) {
(None, None) => None,
(Some(lhs), None) => Some(quote! { #lhs }),
(None, Some(rhs)) => Some(quote! { #rhs }),
(Some(lhs), Some(rhs)) => {
let mut new = lhs.clone();
new.predicates.extend(rhs.predicates.clone());
Some(quote! { #new })
}
};

tokens.append_all(quote! {
#[automatically_derived]
impl #impl_generics cynic::QueryFragment for #target_struct #ty_generics #where_clause {
Expand Down Expand Up @@ -383,6 +470,13 @@ impl quote::ToTokens for SpreadSelection {
}

impl OutputType<'_> {
fn is_composite(&self) -> bool {
matches!(
self,
OutputType::Object(_) | OutputType::Interface(_) | OutputType::Union(_)
)
}

fn as_kind(&self) -> FieldKind {
match self {
OutputType::Scalar(_) => FieldKind::Scalar,
Expand Down
12 changes: 12 additions & 0 deletions cynic-codegen/tests/snapshots/use_schema__simple.graphql.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ expression: "format_code(format!(\"{}\", tokens))"
impl cynic::schema::QueryRoot for Query {}
pub struct AnInputType;
impl cynic::schema::InputObjectMarker for AnInputType {}
pub struct DateTime {}
impl cynic::schema::NamedType for DateTime {
const NAME: &'static ::core::primitive::str = "DateTime";
}
pub struct Dessert {}
pub struct JSON {}
impl cynic::schema::NamedType for JSON {
Expand Down Expand Up @@ -181,6 +185,14 @@ pub mod __fields {
impl cynic::schema::HasField<json> for super::super::TestStruct {
type Type = Option<super::super::JSON>;
}
pub struct date;
impl cynic::schema::Field for date {
type Type = Option<super::super::DateTime>;
const NAME: &'static ::core::primitive::str = "date";
}
impl cynic::schema::HasField<date> for super::super::TestStruct {
type Type = Option<super::super::DateTime>;
}
pub struct __typename;
impl cynic::schema::Field for __typename {
type Type = super::super::String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ expression: parsed.to_sdl()
---
scalar JSON

scalar DateTime

type Query {
testStruct: TestStruct
myUnion: MyUnionType
Expand All @@ -17,6 +19,7 @@ type TestStruct {
optNested: Nested
dessert: Dessert
json: JSON
date: DateTime
}

union MyUnionType = Nested | TestStruct
Expand Down
55 changes: 55 additions & 0 deletions cynic/tests/generics_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,58 @@ fn test_generic_in_response() {

"###);
}

#[derive(cynic::QueryFragment, PartialEq, Debug)]
#[cynic(
schema_path = "../schemas/simple.graphql",
graphql_type = "Query",
variables = "TestArgs"
)]
struct GenericInResponseWithoutBounds<T> {
test_struct: Option<T>,
}

#[test]
fn test_generic_in_response_without_bounds() {
use cynic::QueryBuilder;

let operation =
GenericInResponseWithoutBounds::<TestStruct>::build(TestArgs { a_str: Some("1") });

insta::assert_snapshot!(operation.query, @r###"
query GenericInResponseWithoutBounds($aStr: String) {
testStruct {
fieldOne(x: 1, y: $aStr)
}
}

"###);
}

#[derive(cynic::QueryFragment, PartialEq, Debug)]
#[cynic(schema_path = "../schemas/simple.graphql", graphql_type = "TestStruct")]
struct TestStructWithoutArgs {
field_one: String,
}

#[derive(cynic::QueryFragment, PartialEq, Debug)]
#[cynic(schema_path = "../schemas/simple.graphql", graphql_type = "Query")]
struct GenericInResponseWithoutBoundsOrArgs<T> {
test_struct: Option<T>,
}

#[test]
fn test_generic_in_response_without_bounds_or_args() {
use cynic::QueryBuilder;

let operation = GenericInResponseWithoutBoundsOrArgs::<TestStructWithoutArgs>::build(());

insta::assert_snapshot!(operation.query, @r###"
query GenericInResponseWithoutBoundsOrArgs {
testStruct {
fieldOne
}
}

"###);
}
2 changes: 2 additions & 0 deletions schemas/simple.graphql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
scalar JSON
scalar DateTime

type Query {
testStruct: TestStruct
Expand All @@ -13,6 +14,7 @@ type TestStruct {
optNested: Nested
dessert: Dessert
json: JSON
date: DateTime
}

union MyUnionType = Nested | TestStruct
Expand Down
Loading