Skip to content

Commit

Permalink
Allow using #[derive(GraphQLQuery)] without depending on serde
Browse files Browse the repository at this point in the history
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 = "<path to 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 graphql-rust#427
  • Loading branch information
swlynch99 committed May 30, 2024
1 parent 3090e0a commit 7c1e369
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 13 deletions.
1 change: 0 additions & 1 deletion examples/github/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 0 additions & 1 deletion examples/hasura/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion examples/web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions graphql_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ pub struct Response<Data> {
pub extensions: Option<HashMap<String, serde_json::Value>>,
}

/// Hidden module for types used by the codegen crate.
#[doc(hidden)]
pub mod _private {
pub use ::serde;
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
11 changes: 9 additions & 2 deletions graphql_client_codegen/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,6 +21,8 @@ pub(crate) fn response_for_query(
options: &GraphQLClientCodegenOptions,
query: BoundQuery<'_>,
) -> Result<TokenStream, GeneralError> {
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());
Expand All @@ -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)]
Expand Down Expand Up @@ -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;
);
}
Expand Down Expand Up @@ -115,6 +121,7 @@ fn generate_variables_struct(

let variables_struct = quote!(
#variable_derives
#[serde(crate = #serde_path)]
pub struct Variables {
#(#variable_fields,)*
}
Expand Down
11 changes: 6 additions & 5 deletions graphql_client_codegen/src/codegen/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>(
options: &'a GraphQLClientCodegenOptions,
query: BoundQuery<'schema>,
) -> impl Iterator<Item = TokenStream> + 'a {
let serde = options.serde_path();
let traits = options
.all_response_derives()
.chain(options.all_variable_derives())
Expand Down Expand Up @@ -66,18 +67,18 @@ pub(super) fn generate_enum_definitions<'a, 'schema: 'a>(
Other(String),
}

impl ::serde::Serialize for #name {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
impl #serde::Serialize for #name {
fn serialize<S: #serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(match *self {
#(#constructors => #variant_str,)*
#name::Other(ref s) => &s,
})
}
}

impl<'de> ::serde::Deserialize<'de> for #name {
fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = ::serde::Deserialize::deserialize(deserializer)?;
impl<'de> #serde::Deserialize<'de> for #name {
fn deserialize<D: #serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = #serde::Deserialize::deserialize(deserializer)?;

match s.as_str() {
#(#variant_str => Ok(#constructors),)*
Expand Down
6 changes: 5 additions & 1 deletion graphql_client_codegen/src/codegen/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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());
Expand Down Expand Up @@ -71,6 +74,7 @@ fn generate_struct(

quote! {
#variable_derives
#[serde(crate = #serde_path)]
pub struct #struct_name{
#(#fields,)*
}
Expand Down
8 changes: 6 additions & 2 deletions graphql_client_codegen/src/codegen/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -600,6 +603,7 @@ impl<'a> ExpandedSelection<'a> {

let tokens = quote! {
#response_derives
#[serde(crate = #serde_path)]
pub struct #struct_name {
#(#fields,)*
#on_field
Expand Down
13 changes: 13 additions & 0 deletions graphql_client_codegen/src/codegen_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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),
}
}

Expand Down Expand Up @@ -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
}
}
1 change: 1 addition & 0 deletions graphql_query_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 7c1e369

Please sign in to comment.