Skip to content
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

feat: support skip & include on fields #900

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,8 @@ supported:
- Interfaces & union types
- Introspection via [`cynic-cli`][6] or [`cynic-introspection`][7]
- GraphQL Subscriptions via [`graphql-ws-client`][8].

The following features are not currently supported, but may be one day.

- Directives
- Potentially other things (please open an issue if you find anything obviously
missing)
- Field directives (`@skip`, `@include` and any custom directives that don't
require client support)

### Documentation

Expand Down
10 changes: 10 additions & 0 deletions cynic-codegen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ impl std::iter::FromIterator<syn::Error> for Errors {
}
}

impl IntoIterator for Errors {
type Item = syn::Error;

type IntoIter = std::vec::IntoIter<syn::Error>;

fn into_iter(self) -> Self::IntoIter {
self.errors.into_iter()
}
}

impl From<syn::Error> for Errors {
fn from(err: syn::Error) -> Errors {
Errors { errors: vec![err] }
Expand Down
37 changes: 33 additions & 4 deletions cynic-codegen/src/fragment_derive/arguments/analyse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ use {
};

#[derive(Debug, PartialEq)]
pub struct AnalysedArguments<'a> {
pub struct AnalysedFieldArguments<'a> {
pub schema_field: schema::Field<'a>,
pub arguments: Vec<Field<'a>>,
pub variants: Vec<Rc<VariantDetails<'a>>>,
}

#[derive(Debug, PartialEq)]
pub struct AnalysedDirectiveArguments<'a> {
pub arguments: Vec<Field<'a>>,
pub variants: Vec<Rc<VariantDetails<'a>>>,
}

#[derive(Debug, PartialEq)]
pub struct Object<'a> {
pub schema_obj: InputObjectType<'a>,
Expand Down Expand Up @@ -58,13 +64,13 @@ pub struct VariantDetails<'a> {
pub(super) variant: String,
}

pub fn analyse<'a>(
pub fn analyse_field_arguments<'a>(
schema: &'a Schema<'a, Unvalidated>,
literals: Vec<parsing::FieldArgument>,
field: &schema::Field<'a>,
variables_fields: Option<&syn::Path>,
span: Span,
) -> Result<AnalysedArguments<'a>, Errors> {
) -> Result<AnalysedFieldArguments<'a>, Errors> {
let mut analysis = Analysis {
variables_fields,
variants: HashSet::new(),
Expand All @@ -75,13 +81,36 @@ pub fn analyse<'a>(
let mut variants = analysis.variants.into_iter().collect::<Vec<_>>();
variants.sort_by_key(|v| (v.en.name.clone(), v.variant.clone()));

Ok(AnalysedArguments {
Ok(AnalysedFieldArguments {
schema_field: field.clone(),
arguments,
variants,
})
}

pub fn analyse_directive_arguments<'a>(
schema: &'a Schema<'a, Unvalidated>,
literals: Vec<parsing::FieldArgument>,
directive: &schema::Directive<'a>,
variables_fields: Option<&syn::Path>,
span: Span,
) -> Result<AnalysedDirectiveArguments<'a>, Errors> {
let mut analysis = Analysis {
variables_fields,
variants: HashSet::new(),
};

let arguments = analyse_fields(&mut analysis, literals, &directive.arguments, span, schema)?;

let mut variants = analysis.variants.into_iter().collect::<Vec<_>>();
variants.sort_by_key(|v| (v.en.name.clone(), v.variant.clone()));

Ok(AnalysedDirectiveArguments {
arguments,
variants,
})
}

struct Analysis<'schema, 'a> {
variables_fields: Option<&'a syn::Path>,
variants: HashSet<Rc<VariantDetails<'schema>>>,
Expand Down
14 changes: 10 additions & 4 deletions cynic-codegen/src/fragment_derive/arguments/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod analyse;
mod output;
pub(super) mod analyse;
pub(super) mod output;
mod parsing;

use proc_macro2::Span;
Expand All @@ -9,7 +9,12 @@ use crate::{
schema::{Schema, Unvalidated},
};

pub use self::{output::Output, parsing::arguments_from_field_attrs};
pub use self::{
output::Output,
parsing::{arguments_from_field_attrs, CynicArguments, FieldArgument, FieldArgumentValue},
};

pub(super) use self::parsing::ArgumentLiteral;

pub fn process_arguments<'a>(
schema: &'a Schema<'a, Unvalidated>,
Expand All @@ -19,7 +24,8 @@ pub fn process_arguments<'a>(
variables_fields: Option<&syn::Path>,
span: Span,
) -> Result<Output<'a>, Errors> {
let analysed = analyse::analyse(schema, literals, field, variables_fields, span)?;
let analysed =
analyse::analyse_field_arguments(schema, literals, field, variables_fields, span)?;

Ok(Output {
analysed,
Expand Down
20 changes: 12 additions & 8 deletions cynic-codegen/src/fragment_derive/arguments/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use quote::{format_ident, quote, ToTokens, TokenStreamExt};

use crate::idents::to_pascal_case;

use super::analyse::{AnalysedArguments, ArgumentValue, VariantDetails};
use super::analyse::{AnalysedFieldArguments, ArgumentValue, VariantDetails};

pub struct Output<'a> {
pub(super) analysed: AnalysedArguments<'a>,
pub(super) analysed: AnalysedFieldArguments<'a>,
pub(super) schema_module: syn::Path,
}

Expand Down Expand Up @@ -59,9 +59,9 @@ impl ToTokens for Output<'_> {
}
}

struct ArgumentValueTokens<'a> {
value: &'a ArgumentValue<'a>,
schema_module: &'a syn::Path,
pub struct ArgumentValueTokens<'a> {
pub value: &'a ArgumentValue<'a>,
pub schema_module: &'a syn::Path,
}

impl<'a> ArgumentValueTokens<'a> {
Expand Down Expand Up @@ -138,9 +138,13 @@ impl<'a> VariantDetails<'a> {
}
}

struct VariantDetailsTokens<'a> {
details: &'a VariantDetails<'a>,
schema_module: &'a syn::Path,
/// Tokens for serializing an enum variant literal.
///
/// We can't rely on any types outside of our derive for these so we need to construct
/// individual structs for each variant that we need to serialize.
pub struct VariantDetailsTokens<'a> {
pub details: &'a VariantDetails<'a>,
pub schema_module: &'a syn::Path,
}

impl<'a> quote::ToTokens for VariantDetailsTokens<'a> {
Expand Down
8 changes: 7 additions & 1 deletion cynic-codegen/src/fragment_derive/arguments/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ impl Parse for CynicArguments {
}
}

impl CynicArguments {
pub fn into_inner(self) -> Vec<FieldArgument> {
self.arguments.into_iter().collect()
}
}

#[derive(Debug, Clone)]
pub struct FieldArgument {
pub argument_name: Ident,
Expand Down Expand Up @@ -79,7 +85,7 @@ pub enum ArgumentLiteral {
}

impl ArgumentLiteral {
pub(super) fn span(&self) -> Span {
pub fn span(&self) -> Span {
match self {
ArgumentLiteral::Literal(lit) => lit.span(),
ArgumentLiteral::Enum(ident) => ident.span(),
Expand Down
13 changes: 9 additions & 4 deletions cynic-codegen/src/fragment_derive/arguments/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::parse_quote;

use crate::schema::{types::Type, Schema, SchemaInput};

use super::{analyse::analyse, parsing::CynicArguments};
use super::{analyse::analyse_field_arguments, parsing::CynicArguments};

#[rstest]
#[case::scalars("scalars", "someScalarParams", parse_quote! { anInt: 1, aFloat: 3, anId: "hello" })]
Expand Down Expand Up @@ -34,7 +34,7 @@ fn test_analyse(

insta::assert_debug_snapshot!(
snapshot_name,
analyse(
analyse_field_arguments(
&schema,
literals,
field,
Expand All @@ -55,9 +55,14 @@ fn test_analyse_errors_without_argument_struct() {
parse_quote! { filters: $aVaraible, optionalFilters: $anotherVar };
let literals = literals.arguments.into_iter().collect::<Vec<_>>();

insta::assert_debug_snapshot!(
analyse(&schema, literals, field, None, Span::call_site()).map(|o| o.arguments)
insta::assert_debug_snapshot!(analyse_field_arguments(
&schema,
literals,
field,
None,
Span::call_site()
)
.map(|o| o.arguments))
}

const SCHEMA: &str = r#"
Expand Down
20 changes: 13 additions & 7 deletions cynic-codegen/src/fragment_derive/deserialize_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct Field {
is_flattened: bool,
is_recurse: bool,
is_feature_flagged: bool,
is_skippable: bool,
}

impl<'a> DeserializeImpl<'a> {
Expand All @@ -40,7 +41,7 @@ impl<'a> DeserializeImpl<'a> {
name: &'a syn::Ident,
generics: &'a syn::Generics,
) -> Self {
let spreading = fields.iter().any(|f| *f.0.spread);
let spreading = fields.iter().any(|f| f.0.spread());

let target_struct = name;
let fields = fields
Expand Down Expand Up @@ -123,6 +124,10 @@ impl quote::ToTokens for StandardDeserializeImpl<'_> {
quote_spanned!{ span =>
let #rust_name = #rust_name.unwrap_or_default();
}
} else if field.is_skippable {
quote! {
let #rust_name = #rust_name.unwrap_or_default();
}
} else {
quote! {
let #rust_name = #rust_name.ok_or_else(|| cynic::serde::de::Error::missing_field(#serialized_name))?;
Expand Down Expand Up @@ -269,7 +274,7 @@ impl quote::ToTokens for SpreadingDeserializeImpl<'_> {

fn process_field(field: &FragmentDeriveField, schema_field: Option<&schema::Field<'_>>) -> Field {
// Should be ok to unwrap since we only accept struct style input
let rust_name = field.ident.as_ref().unwrap();
let rust_name = field.ident().unwrap();
let field_variant_name = rust_name.clone();

Field {
Expand All @@ -278,10 +283,11 @@ fn process_field(field: &FragmentDeriveField, schema_field: Option<&schema::Fiel
.alias()
.or_else(|| schema_field.map(|f| f.name.as_str().to_string())),
rust_name: rust_name.clone(),
ty: field.ty.clone(),
is_spread: *field.spread,
is_flattened: *field.flatten,
is_recurse: field.recurse.is_some(),
is_feature_flagged: field.feature.is_some(),
ty: field.raw_field.ty.clone(),
is_spread: field.spread(),
is_flattened: *field.raw_field.flatten,
is_recurse: field.raw_field.recurse.is_some(),
is_feature_flagged: field.raw_field.feature.is_some(),
is_skippable: field.is_skippable(),
}
}
Loading
Loading