cynic_codegen/fragment_derive/
mod.rs

1use proc_macro2::TokenStream;
2use quote::ToTokens;
3
4use crate::{
5    schema::{
6        types::{self as schema},
7        Schema,
8    },
9    suggestions::FieldSuggestionError,
10    Errors,
11};
12
13mod arguments;
14mod deserialize_impl;
15mod directives;
16mod fragment_derive_type;
17mod fragment_impl;
18mod type_ext;
19
20pub(crate) mod input;
21
22use self::{
23    deserialize_impl::DeserializeImpl, fragment_derive_type::FragmentDeriveType,
24    fragment_impl::FragmentImpl,
25};
26
27pub use input::{FragmentDeriveField, FragmentDeriveInput};
28
29use crate::suggestions::guess_field;
30
31pub fn fragment_derive(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
32    use darling::FromDeriveInput;
33
34    match FragmentDeriveInput::from_derive_input(ast) {
35        Ok(input) => fragment_derive_impl(input).or_else(|e| Ok(e.to_compile_errors())),
36        Err(e) => Ok(e.write_errors()),
37    }
38}
39
40pub fn fragment_derive_impl(input: FragmentDeriveInput) -> Result<TokenStream, Errors> {
41    let mut input = input;
42
43    input.detect_aliases();
44    let fields = input.validate()?;
45
46    let schema = Schema::new(input.schema_input()?);
47
48    let schema_type = schema
49        .lookup::<FragmentDeriveType<'_>>(&input.graphql_type_name())
50        .map_err(|e| syn::Error::new(input.graphql_type_span(), e))?;
51
52    let graphql_name = &(input.graphql_type_name());
53    let schema_module = input.schema_module();
54    let variables = input.variables();
55    let fields = pair_fields(fields.into_iter(), &schema_type)?;
56
57    let fragment_impl = FragmentImpl::new_for(
58        &schema,
59        &fields,
60        &input.ident,
61        &input.generics,
62        &schema_type,
63        &schema_module,
64        graphql_name,
65        variables.as_ref(),
66    )?;
67
68    let deserialize_impl = DeserializeImpl::new(&fields, &input.ident, &input.generics);
69
70    let mut output = TokenStream::new();
71    fragment_impl.to_tokens(&mut output);
72    if !input.no_deserialize {
73        deserialize_impl.to_tokens(&mut output);
74    }
75
76    Ok(output)
77}
78
79fn pair_fields<'a>(
80    rust_fields: impl IntoIterator<Item = FragmentDeriveField>,
81    schema_type: &FragmentDeriveType<'a>,
82) -> Result<Vec<(FragmentDeriveField, Option<schema::Field<'a>>)>, Errors> {
83    let mut result = Vec::new();
84    let mut unknown_fields = Vec::new();
85    for field in rust_fields {
86        let ident = field.graphql_ident();
87        match (schema_type.field(&ident), field.spread()) {
88            (Some(schema_field), _) => result.push((field, Some(schema_field.clone()))),
89            (None, false) => unknown_fields.push(ident),
90            (None, true) => result.push((field, None)),
91        }
92    }
93
94    if unknown_fields.is_empty() {
95        return Ok(result);
96    }
97
98    let field_candidates = schema_type
99        .fields
100        .iter()
101        .map(|f| f.name.as_str())
102        .collect::<Vec<_>>();
103
104    let errors = unknown_fields
105        .into_iter()
106        .map(|field| {
107            let expected_field = &field.graphql_name();
108            let suggested_field = guess_field(field_candidates.iter().copied(), expected_field);
109            syn::Error::new(
110                field.span(),
111                FieldSuggestionError {
112                    expected_field,
113                    graphql_type_name: schema_type.name.as_ref(),
114                    suggested_field,
115                },
116            )
117        })
118        .map(Errors::from)
119        .collect();
120
121    Err(errors)
122}
123
124#[cfg(test)]
125mod tests;