cynic_codegen/fragment_derive/
mod.rs1use 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;