From 7c1e369868cec9135ca34ab99d46e455ceee2f75 Mon Sep 17 00:00:00 2001 From: Sean Lynch <42618346+swlynch99@users.noreply.github.com> Date: Wed, 29 May 2024 17:21:48 -0700 Subject: [PATCH] Allow using #[derive(GraphQLQuery)] without depending on serde This PR makes the serde derives emitted by `#[derive(GraphQLQuery)]` use a private re-export of the serde crate instead of relying on users of this crate having added serde to their `Cargo.toml`. Serde allows you to add an annotation #[serde(crate = "")] for exactly this reason so most of this PR is just plumbing the correct path through the codegen crate to the correct annotation. Details ------- - There is a new `#[doc(hidden)] mod _private` declaration in the `graphql_client` crate. All re-exports (which is just serde) have been placed there. - I have added a new `serde_path` field to `GraphQLCodegenOptions` which defaults to `::serde`. This means that the code generated by the CLI should remain effectively unchanged. - The rest is just applying `#[serde(crate = ...)]` annotations where appropriate. Fixes #427 --- examples/github/Cargo.toml | 1 - examples/hasura/Cargo.toml | 1 - examples/web/Cargo.toml | 1 - graphql_client/src/lib.rs | 6 ++++++ graphql_client_codegen/src/codegen.rs | 11 +++++++++-- graphql_client_codegen/src/codegen/enums.rs | 11 ++++++----- graphql_client_codegen/src/codegen/inputs.rs | 6 +++++- graphql_client_codegen/src/codegen/selection.rs | 8 ++++++-- graphql_client_codegen/src/codegen_options.rs | 13 +++++++++++++ graphql_query_derive/src/lib.rs | 1 + 10 files changed, 46 insertions(+), 13 deletions(-) diff --git a/examples/github/Cargo.toml b/examples/github/Cargo.toml index 663fc0fc..48294ab8 100644 --- a/examples/github/Cargo.toml +++ b/examples/github/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dev-dependencies] anyhow = "1.0" graphql_client = { path = "../../graphql_client", features = ["reqwest-blocking"] } -serde = "^1.0" reqwest = { version = "^0.11", features = ["json", "blocking"] } prettytable-rs = "^0.7" clap = { version = "^3.0", features = ["derive"] } diff --git a/examples/hasura/Cargo.toml b/examples/hasura/Cargo.toml index 56ca334c..fcfb7394 100644 --- a/examples/hasura/Cargo.toml +++ b/examples/hasura/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dev-dependencies] anyhow = "1.0" graphql_client = { path = "../../graphql_client", features = ["reqwest-blocking"] } -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" reqwest = { version = "^0.11", features = ["json", "blocking"] } prettytable-rs = "0.7.0" diff --git a/examples/web/Cargo.toml b/examples/web/Cargo.toml index 76a0a292..14864092 100644 --- a/examples/web/Cargo.toml +++ b/examples/web/Cargo.toml @@ -10,7 +10,6 @@ crate-type = ["cdylib", "rlib"] [dependencies] graphql_client = { path = "../../graphql_client", features = ["reqwest"] } wasm-bindgen = "^0.2" -serde = { version = "1.0.67", features = ["derive"] } lazy_static = "1.0.1" js-sys = "0.3.6" wasm-bindgen-futures = "0.4.18" diff --git a/graphql_client/src/lib.rs b/graphql_client/src/lib.rs index d4856fac..7b4c9683 100644 --- a/graphql_client/src/lib.rs +++ b/graphql_client/src/lib.rs @@ -299,6 +299,12 @@ pub struct Response { pub extensions: Option>, } +/// Hidden module for types used by the codegen crate. +#[doc(hidden)] +pub mod _private { + pub use ::serde; +} + #[cfg(test)] mod tests { use super::*; diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index f3ad1a94..ef74c71b 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -11,7 +11,7 @@ use crate::{ }; use heck::ToSnakeCase; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use selection::*; use std::collections::BTreeMap; @@ -21,6 +21,8 @@ pub(crate) fn response_for_query( options: &GraphQLClientCodegenOptions, query: BoundQuery<'_>, ) -> Result { + let serde = options.serde_path(); + let all_used_types = all_used_types(operation_id, &query); let response_derives = render_derives(options.all_response_derives()); let variable_derives = render_derives(options.all_variable_derives()); @@ -43,7 +45,7 @@ pub(crate) fn response_for_query( render_response_data_fields(operation_id, options, &query).render(&response_derives); let q = quote! { - use serde::{Serialize, Deserialize}; + use #serde::{Serialize, Deserialize}; use super::*; #[allow(dead_code)] @@ -77,9 +79,13 @@ fn generate_variables_struct( options: &GraphQLClientCodegenOptions, query: &BoundQuery<'_>, ) -> TokenStream { + let serde = options.serde_path(); + let serde_path = serde.to_token_stream().to_string(); + if operation_has_no_variables(operation_id, query.query) { return quote!( #variable_derives + #[serde(crate = #serde_path)] pub struct Variables; ); } @@ -115,6 +121,7 @@ fn generate_variables_struct( let variables_struct = quote!( #variable_derives + #[serde(crate = #serde_path)] pub struct Variables { #(#variable_fields,)* } diff --git a/graphql_client_codegen/src/codegen/enums.rs b/graphql_client_codegen/src/codegen/enums.rs index e990d02c..adc59a89 100644 --- a/graphql_client_codegen/src/codegen/enums.rs +++ b/graphql_client_codegen/src/codegen/enums.rs @@ -16,6 +16,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( options: &'a GraphQLClientCodegenOptions, query: BoundQuery<'schema>, ) -> impl Iterator + 'a { + let serde = options.serde_path(); let traits = options .all_response_derives() .chain(options.all_variable_derives()) @@ -66,8 +67,8 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( Other(String), } - impl ::serde::Serialize for #name { - fn serialize(&self, ser: S) -> Result { + impl #serde::Serialize for #name { + fn serialize(&self, ser: S) -> Result { ser.serialize_str(match *self { #(#constructors => #variant_str,)* #name::Other(ref s) => &s, @@ -75,9 +76,9 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>( } } - impl<'de> ::serde::Deserialize<'de> for #name { - fn deserialize>(deserializer: D) -> Result { - let s: String = ::serde::Deserialize::deserialize(deserializer)?; + impl<'de> #serde::Deserialize<'de> for #name { + fn deserialize>(deserializer: D) -> Result { + let s: String = #serde::Deserialize::deserialize(deserializer)?; match s.as_str() { #(#variant_str => Ok(#constructors),)* diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 475ad171..adf0b2db 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -7,7 +7,7 @@ use crate::{ }; use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; pub(super) fn generate_input_object_definitions( all_used_types: &UsedTypes, @@ -33,6 +33,9 @@ fn generate_struct( variable_derives: &impl quote::ToTokens, query: &BoundQuery<'_>, ) -> TokenStream { + let serde = options.serde_path(); + let serde_path = serde.to_token_stream().to_string(); + let normalized_name = options.normalization().input_name(input.name.as_str()); let safe_name = keyword_replace(normalized_name); let struct_name = Ident::new(safe_name.as_ref(), Span::call_site()); @@ -71,6 +74,7 @@ fn generate_struct( quote! { #variable_derives + #[serde(crate = #serde_path)] pub struct #struct_name{ #(#fields,)* } diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 5dd72e28..4dd46d96 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -16,7 +16,7 @@ use crate::{ }; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use std::borrow::Cow; pub(crate) fn render_response_data_fields<'a>( @@ -433,7 +433,7 @@ impl<'a> ExpandedField<'a> { (Some(msg), DeprecationStrategy::Warn) => { let optional_msg = msg.map(|msg| quote!((note = #msg))); - Some(quote!(#[deprecated#optional_msg])) + Some(quote!(#[deprecated #optional_msg])) } (Some(_), DeprecationStrategy::Deny) => return None, }; @@ -532,6 +532,9 @@ impl<'a> ExpandedSelection<'a> { } pub fn render(&self, response_derives: &impl quote::ToTokens) -> TokenStream { + let serde = self.options.serde_path(); + let serde_path = serde.to_token_stream().to_string(); + let mut items = Vec::with_capacity(self.types.len()); for (type_id, ty) in self.types() { @@ -600,6 +603,7 @@ impl<'a> ExpandedSelection<'a> { let tokens = quote! { #response_derives + #[serde(crate = #serde_path)] pub struct #struct_name { #(#fields,)* #on_field diff --git a/graphql_client_codegen/src/codegen_options.rs b/graphql_client_codegen/src/codegen_options.rs index 164b0dd7..006ae727 100644 --- a/graphql_client_codegen/src/codegen_options.rs +++ b/graphql_client_codegen/src/codegen_options.rs @@ -47,6 +47,8 @@ pub struct GraphQLClientCodegenOptions { fragments_other_variant: bool, /// Skip Serialization of None values. skip_serializing_none: bool, + /// Path to the serde crate. + serde_path: syn::Path, } impl GraphQLClientCodegenOptions { @@ -68,6 +70,7 @@ impl GraphQLClientCodegenOptions { extern_enums: Default::default(), fragments_other_variant: Default::default(), skip_serializing_none: Default::default(), + serde_path: syn::parse_quote!(::serde), } } @@ -227,4 +230,14 @@ impl GraphQLClientCodegenOptions { pub fn skip_serializing_none(&self) -> &bool { &self.skip_serializing_none } + + /// Set the path to used to resolve serde traits. + pub fn set_serde_path(&mut self, path: syn::Path) { + self.serde_path = path; + } + + /// Get a reference to the path used to resolve serde traits. + pub fn serde_path(&self) -> &syn::Path { + &self.serde_path + } } diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index f335aa79..6e1ef37a 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -104,6 +104,7 @@ fn build_graphql_client_derive_options( options.set_struct_ident(input.ident.clone()); options.set_module_visibility(input.vis.clone()); options.set_operation_name(input.ident.to_string()); + options.set_serde_path(syn::parse_quote!(::graphql_client::_private::serde)); Ok(options) }