cynic-codegen 3.13.2

Procedural macro code generation for cynic - a code first GraphQL client for Rust
Documentation
use proc_macro2::TokenStream;
use quote::ToTokens;

use crate::{
    Errors,
    idents::RenameAll,
    schema::{
        Schema,
        types::{self as schema},
    },
    suggestions::FieldSuggestionError,
};

mod arguments;
mod deserialize_impl;
mod directives;
mod fragment_derive_type;
mod fragment_impl;
mod type_ext;

pub(crate) mod input;

use self::{
    deserialize_impl::DeserializeImpl, fragment_derive_type::FragmentDeriveType,
    fragment_impl::FragmentImpl,
};

pub use input::{FragmentDeriveField, FragmentDeriveInput};

use crate::suggestions::guess_field;

pub fn fragment_derive(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    use darling::FromDeriveInput;

    match FragmentDeriveInput::from_derive_input(ast) {
        Ok(input) => fragment_derive_impl(input).or_else(|e| Ok(e.to_compile_errors())),
        Err(e) => Ok(e.write_errors()),
    }
}

pub fn fragment_derive_impl(input: FragmentDeriveInput) -> Result<TokenStream, Errors> {
    let mut input = input;

    input.detect_aliases();
    let fields = input.validate()?;

    let schema = Schema::new(input.schema_input()?);

    let schema_type = schema
        .lookup::<FragmentDeriveType<'_>>(&input.graphql_type_name())
        .map_err(|e| syn::Error::new(input.graphql_type_span(), e))?;

    let rename_all = input.rename_all.unwrap_or(RenameAll::CamelCase);
    let graphql_name = &(input.graphql_type_name());
    let schema_module = input.schema_module();
    let variables = input.variables();
    let fields = pair_fields(fields.into_iter(), rename_all, &schema_type)?;

    let fragment_impl = FragmentImpl::new_for(
        &schema,
        &fields,
        &input.ident,
        &input.generics,
        &schema_type,
        &schema_module,
        graphql_name,
        variables.as_ref(),
    )?;

    let deserialize_impl = DeserializeImpl::new(&fields, &input.ident, &input.generics);

    let mut output = TokenStream::new();
    fragment_impl.to_tokens(&mut output);
    if !input.no_deserialize {
        deserialize_impl.to_tokens(&mut output);
    }

    Ok(output)
}

fn pair_fields<'a>(
    rust_fields: impl IntoIterator<Item = FragmentDeriveField>,
    rename_all: RenameAll,
    schema_type: &FragmentDeriveType<'a>,
) -> Result<Vec<(FragmentDeriveField, Option<schema::Field<'a>>)>, Errors> {
    let mut result = Vec::new();
    let mut unknown_fields = Vec::new();
    for field in rust_fields {
        let ident = field.graphql_ident(rename_all);
        match (schema_type.field(&ident), field.spread()) {
            (Some(schema_field), _) => result.push((field, Some(schema_field.clone()))),
            (None, false) => unknown_fields.push(ident),
            (None, true) => result.push((field, None)),
        }
    }

    if unknown_fields.is_empty() {
        return Ok(result);
    }

    let field_candidates = schema_type
        .fields
        .iter()
        .map(|f| f.name.as_str())
        .collect::<Vec<_>>();

    let errors = unknown_fields
        .into_iter()
        .map(|field| {
            let expected_field = &field.graphql_name();
            let suggested_field = guess_field(field_candidates.iter().copied(), expected_field);
            syn::Error::new(
                field.span(),
                FieldSuggestionError {
                    expected_field,
                    graphql_type_name: schema_type.name.as_ref(),
                    suggested_field,
                },
            )
        })
        .map(Errors::from)
        .collect();

    Err(errors)
}

#[cfg(test)]
mod tests;