From 30292e5da90eda5adcdea982621fedbce655e52e Mon Sep 17 00:00:00 2001 From: Luuk de Waal Malefijt Date: Thu, 9 Oct 2025 09:20:49 +0200 Subject: [PATCH 1/4] fix(codegen): preserve leading underscores in field names Fixes issue where GraphQL fields with leading underscores (like `_id`) were incorrectly converted to snake_case without preserving the prefix. Changes: - Added `to_snake_case_preserve_leading_underscores()` helper function - Updated all heck::to_snake_case() calls in codegen to use the wrapper - Preserves leading underscores for: - Input object field names - Response type field names - Fragment names - Variable names - Module names This fixes compatibility with TerminusDB GraphQL schema which uses `_id` fields that should remain as `_id` in generated Rust code. Related to: https://github.com/graphql-rust/graphql-client/issues/498 --- graphql_client_codegen/src/codegen.rs | 15 ++++++++++++++- graphql_client_codegen/src/codegen/inputs.rs | 15 ++++++++++++++- .../src/codegen/selection.rs | 19 ++++++++++++++++--- .../src/generated_module.rs | 15 ++++++++++++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index e33bb1b1..22da7dd1 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -15,6 +15,19 @@ use quote::{quote, ToTokens}; use selection::*; use std::collections::BTreeMap; +/// Convert to snake_case while preserving leading underscores. +/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. +fn to_snake_case_preserve_leading_underscores(s: &str) -> String { + let leading_underscores = s.chars().take_while(|&c| c == '_').count(); + if leading_underscores == 0 { + s.to_snake_case() + } else { + let prefix = "_".repeat(leading_underscores); + let rest = &s[leading_underscores..]; + format!("{}{}", prefix, rest.to_snake_case()) + } +} + /// The main code generation function. pub(crate) fn response_for_query( operation_id: OperationId, @@ -139,7 +152,7 @@ fn generate_variable_struct_field( options: &GraphQLClientCodegenOptions, query: &BoundQuery<'_>, ) -> TokenStream { - let snake_case_name = variable.name.to_snake_case(); + let snake_case_name = to_snake_case_preserve_leading_underscores(&variable.name); let safe_name = shared::keyword_replace(&snake_case_name); let ident = Ident::new(&safe_name, Span::call_site()); let rename_annotation = shared::field_rename_annotation(&variable.name, &safe_name); diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index d8cc1080..a0e89de9 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -9,6 +9,19 @@ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; +/// Convert to snake_case while preserving leading underscores. +/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. +fn to_snake_case_preserve_leading_underscores(s: &str) -> String { + let leading_underscores = s.chars().take_while(|&c| c == '_').count(); + if leading_underscores == 0 { + s.to_snake_case() + } else { + let prefix = "_".repeat(leading_underscores); + let rest = &s[leading_underscores..]; + format!("{}{}", prefix, rest.to_snake_case()) + } +} + pub(super) fn generate_input_object_definitions( all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, @@ -61,7 +74,7 @@ fn generate_struct( let struct_name = Ident::new(safe_name.as_ref(), Span::call_site()); let fields = input.fields.iter().map(|(field_name, field_type)| { - let safe_field_name = keyword_replace(field_name.to_snake_case()); + let safe_field_name = keyword_replace(to_snake_case_preserve_leading_underscores(field_name)); let annotation = field_rename_annotation(field_name, safe_field_name.as_ref()); let name_ident = Ident::new(safe_field_name.as_ref(), Span::call_site()); let normalized_field_type_name = options diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index ec1703b8..31a0446b 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -21,6 +21,19 @@ use quote::{quote, ToTokens}; use std::borrow::Cow; use syn::Path; +/// Convert to snake_case while preserving leading underscores. +/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. +fn to_snake_case_preserve_leading_underscores(s: &str) -> String { + let leading_underscores = s.chars().take_while(|&c| c == '_').count(); + if leading_underscores == 0 { + s.to_snake_case() + } else { + let prefix = "_".repeat(leading_underscores); + let rest = &s[leading_underscores..]; + format!("{}{}", prefix, rest.to_snake_case()) + } +} + pub(crate) fn render_response_data_fields<'a>( operation_id: OperationId, options: &'a GraphQLClientCodegenOptions, @@ -281,7 +294,7 @@ fn calculate_selection<'a>( field_type_qualifiers: &[GraphqlTypeQualifier::Required], flatten: true, graphql_name: None, - rust_name: fragment.name.to_snake_case().into(), + rust_name: to_snake_case_preserve_leading_underscores(&fragment.name).into(), struct_id, deprecation: None, boxed: fragment_is_recursive(*fragment_id, context.query.query), @@ -395,7 +408,7 @@ fn calculate_selection<'a>( continue; } - let original_field_name = fragment.name.to_snake_case(); + let original_field_name = to_snake_case_preserve_leading_underscores(&fragment.name); let final_field_name = keyword_replace(original_field_name); context.push_field(ExpandedField { @@ -578,7 +591,7 @@ impl<'a> ExpandedSelection<'a> { let name = field .alias() .unwrap_or_else(|| &field.schema_field(self.query.schema).name); - let snake_case_name = name.to_snake_case(); + let snake_case_name = to_snake_case_preserve_leading_underscores(name); let final_name = keyword_replace(snake_case_name); (name, final_name) diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index b225d001..96f3e7d3 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -8,6 +8,19 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::{error::Error, fmt::Display}; +/// Convert to snake_case while preserving leading underscores. +/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. +fn to_snake_case_preserve_leading_underscores(s: &str) -> String { + let leading_underscores = s.chars().take_while(|&c| c == '_').count(); + if leading_underscores == 0 { + s.to_snake_case() + } else { + let prefix = "_".repeat(leading_underscores); + let rest = &s[leading_underscores..]; + format!("{}{}", prefix, rest.to_snake_case()) + } +} + #[derive(Debug)] struct OperationNotFound { operation_name: String, @@ -57,7 +70,7 @@ impl GeneratedModule<'_> { /// Generate the module and all the code inside. pub(crate) fn to_token_stream(&self) -> Result { - let module_name = Ident::new(&self.operation.to_snake_case(), Span::call_site()); + let module_name = Ident::new(&to_snake_case_preserve_leading_underscores(self.operation), Span::call_site()); let module_visibility = &self.options.module_visibility(); let operation_name = self.operation; let operation_name_ident = self.options.normalization().operation(self.operation); From 32611bb3f270be57c615c5629a3fa46afe03e18d Mon Sep 17 00:00:00 2001 From: Luuk de Waal Malefijt Date: Thu, 9 Oct 2025 09:26:56 +0200 Subject: [PATCH 2/4] refactor: centralize to_snake_case_preserve_leading_underscores helper Moved the duplicated helper function from 4 separate modules into the shared module to eliminate code duplication. Changes: - Added helper to graphql_client_codegen/src/codegen/shared.rs - Made shared module pub(crate) to allow access from generated_module.rs - Removed duplicates from inputs.rs, selection.rs, codegen.rs, generated_module.rs - Updated all modules to import from shared module No functional changes, purely code organization improvement. --- graphql_client_codegen/src/codegen.rs | 16 ++-------------- graphql_client_codegen/src/codegen/inputs.rs | 15 +-------------- graphql_client_codegen/src/codegen/selection.rs | 15 +-------------- graphql_client_codegen/src/codegen/shared.rs | 14 ++++++++++++++ graphql_client_codegen/src/generated_module.rs | 14 +------------- 5 files changed, 19 insertions(+), 55 deletions(-) diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index 22da7dd1..aae35dfa 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -1,7 +1,7 @@ mod enums; mod inputs; mod selection; -mod shared; +pub(crate) mod shared; use crate::{ query::*, @@ -13,21 +13,9 @@ use heck::ToSnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use selection::*; +use shared::to_snake_case_preserve_leading_underscores; use std::collections::BTreeMap; -/// Convert to snake_case while preserving leading underscores. -/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. -fn to_snake_case_preserve_leading_underscores(s: &str) -> String { - let leading_underscores = s.chars().take_while(|&c| c == '_').count(); - if leading_underscores == 0 { - s.to_snake_case() - } else { - let prefix = "_".repeat(leading_underscores); - let rest = &s[leading_underscores..]; - format!("{}{}", prefix, rest.to_snake_case()) - } -} - /// The main code generation function. pub(crate) fn response_for_query( operation_id: OperationId, diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index a0e89de9..37e33b32 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,4 +1,4 @@ -use super::shared::{field_rename_annotation, keyword_replace}; +use super::shared::{field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores}; use crate::{ codegen_options::GraphQLClientCodegenOptions, query::{BoundQuery, UsedTypes}, @@ -9,19 +9,6 @@ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -/// Convert to snake_case while preserving leading underscores. -/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. -fn to_snake_case_preserve_leading_underscores(s: &str) -> String { - let leading_underscores = s.chars().take_while(|&c| c == '_').count(); - if leading_underscores == 0 { - s.to_snake_case() - } else { - let prefix = "_".repeat(leading_underscores); - let rest = &s[leading_underscores..]; - format!("{}{}", prefix, rest.to_snake_case()) - } -} - pub(super) fn generate_input_object_definitions( all_used_types: &UsedTypes, options: &GraphQLClientCodegenOptions, diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 31a0446b..bab13e91 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -3,7 +3,7 @@ use crate::{ codegen::{ decorate_type, - shared::{field_rename_annotation, keyword_replace}, + shared::{field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores}, }, deprecation::DeprecationStrategy, query::{ @@ -21,19 +21,6 @@ use quote::{quote, ToTokens}; use std::borrow::Cow; use syn::Path; -/// Convert to snake_case while preserving leading underscores. -/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. -fn to_snake_case_preserve_leading_underscores(s: &str) -> String { - let leading_underscores = s.chars().take_while(|&c| c == '_').count(); - if leading_underscores == 0 { - s.to_snake_case() - } else { - let prefix = "_".repeat(leading_underscores); - let rest = &s[leading_underscores..]; - format!("{}{}", prefix, rest.to_snake_case()) - } -} - pub(crate) fn render_response_data_fields<'a>( operation_id: OperationId, options: &'a GraphQLClientCodegenOptions, diff --git a/graphql_client_codegen/src/codegen/shared.rs b/graphql_client_codegen/src/codegen/shared.rs index 9bffe3bf..5ea085bd 100644 --- a/graphql_client_codegen/src/codegen/shared.rs +++ b/graphql_client_codegen/src/codegen/shared.rs @@ -1,7 +1,21 @@ +use heck::ToSnakeCase; use proc_macro2::TokenStream; use quote::quote; use std::borrow::Cow; +/// Convert to snake_case while preserving leading underscores. +/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. +pub(crate) fn to_snake_case_preserve_leading_underscores(s: &str) -> String { + let leading_underscores = s.chars().take_while(|&c| c == '_').count(); + if leading_underscores == 0 { + s.to_snake_case() + } else { + let prefix = "_".repeat(leading_underscores); + let rest = &s[leading_underscores..]; + format!("{}{}", prefix, rest.to_snake_case()) + } +} + // List of keywords based on https://doc.rust-lang.org/reference/keywords.html // code snippet: `[...new Set($$("code.hljs").map(x => x.textContent).filter(x => x.match(/^[_a-z0-9]+$/i)))].sort()` const RUST_KEYWORDS: &[&str] = &[ diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 96f3e7d3..a7705e89 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -1,4 +1,5 @@ use crate::{ + codegen::shared::to_snake_case_preserve_leading_underscores, codegen_options::*, query::{BoundQuery, OperationId}, BoxError, @@ -8,19 +9,6 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::{error::Error, fmt::Display}; -/// Convert to snake_case while preserving leading underscores. -/// This is important for GraphQL fields like `_id` which should become `_id` not `id`. -fn to_snake_case_preserve_leading_underscores(s: &str) -> String { - let leading_underscores = s.chars().take_while(|&c| c == '_').count(); - if leading_underscores == 0 { - s.to_snake_case() - } else { - let prefix = "_".repeat(leading_underscores); - let rest = &s[leading_underscores..]; - format!("{}{}", prefix, rest.to_snake_case()) - } -} - #[derive(Debug)] struct OperationNotFound { operation_name: String, From 2065f0c701b3586231bed4a5b19064d1d9abfa7b Mon Sep 17 00:00:00 2001 From: Luuk de Waal Malefijt Date: Fri, 10 Oct 2025 00:14:16 +0200 Subject: [PATCH 3/4] chore: remove unused imports and fix test for leading underscores After centralizing the to_snake_case_preserve_leading_underscores helper function, several unused imports of heck traits remained in the codebase. This commit removes those unused imports to clean up compiler warnings. Changes: - Removed unused ToSnakeCase import from codegen/inputs.rs - Removed unused heck::* import from codegen/selection.rs - Removed unused heck::ToSnakeCase import from codegen.rs - Removed unused heck::* import from generated_module.rs - Updated introspection test to use __schema field name instead of schema, correctly validating that leading underscores are preserved All tests pass: cargo test --all --tests --examples --- graphql_client/tests/introspection.rs | 2 +- graphql_client_codegen/src/codegen.rs | 1 - graphql_client_codegen/src/codegen/inputs.rs | 2 +- graphql_client_codegen/src/codegen/selection.rs | 1 - graphql_client_codegen/src/generated_module.rs | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/graphql_client/tests/introspection.rs b/graphql_client/tests/introspection.rs index acb5e9dd..c701be64 100644 --- a/graphql_client/tests/introspection.rs +++ b/graphql_client/tests/introspection.rs @@ -18,5 +18,5 @@ fn leading_underscores_are_preserved() { let deserialized: graphql_client::Response = serde_json::from_str(INTROSPECTION_RESPONSE).unwrap(); assert!(deserialized.data.is_some()); - assert!(deserialized.data.unwrap().schema.is_some()); + assert!(deserialized.data.unwrap().__schema.is_some()); } diff --git a/graphql_client_codegen/src/codegen.rs b/graphql_client_codegen/src/codegen.rs index aae35dfa..ce55394b 100644 --- a/graphql_client_codegen/src/codegen.rs +++ b/graphql_client_codegen/src/codegen.rs @@ -9,7 +9,6 @@ use crate::{ type_qualifiers::GraphqlTypeQualifier, GeneralError, GraphQLClientCodegenOptions, }; -use heck::ToSnakeCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use selection::*; diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index 37e33b32..e572b128 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -5,7 +5,7 @@ use crate::{ schema::{input_is_recursive_without_indirection, StoredInputType}, type_qualifiers::GraphqlTypeQualifier, }; -use heck::{ToSnakeCase, ToUpperCamelCase}; +use heck::ToUpperCamelCase; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index bab13e91..243de0bd 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -15,7 +15,6 @@ use crate::{ GraphQLClientCodegenOptions, GeneralError, }; -use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; use std::borrow::Cow; diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index a7705e89..717cacbc 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -4,7 +4,6 @@ use crate::{ query::{BoundQuery, OperationId}, BoxError, }; -use heck::*; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use std::{error::Error, fmt::Display}; From 3931f2d36574dc2aba99b9741ecc47f3d16cbbdb Mon Sep 17 00:00:00 2001 From: Luuk de Waal Malefijt Date: Fri, 10 Oct 2025 00:16:30 +0200 Subject: [PATCH 4/4] chore: run cargo fmt to fix formatting issues Apply rustfmt formatting to all files to comply with project style guidelines. --- graphql_client_cli/src/generate.rs | 8 ++-- graphql_client_cli/src/main.rs | 4 +- graphql_client_codegen/src/codegen/inputs.rs | 14 +++++-- .../src/codegen/selection.rs | 42 ++++++++++++++----- .../src/generated_module.rs | 5 ++- graphql_client_codegen/src/tests/mod.rs | 3 +- graphql_query_derive/src/lib.rs | 2 +- 7 files changed, 55 insertions(+), 23 deletions(-) diff --git a/graphql_client_cli/src/generate.rs b/graphql_client_cli/src/generate.rs index 1a36d0cf..f6fe05fe 100644 --- a/graphql_client_cli/src/generate.rs +++ b/graphql_client_cli/src/generate.rs @@ -93,11 +93,13 @@ pub(crate) fn generate_code(params: CliCodegenParams) -> CliResult<()> { options.set_custom_scalars_module(custom_scalars_module); } - + if let Some(custom_variable_types) = custom_variable_types { - options.set_custom_variable_types(custom_variable_types.split(",").map(String::from).collect()); + options.set_custom_variable_types( + custom_variable_types.split(",").map(String::from).collect(), + ); } - + if let Some(custom_response_type) = custom_response_type { options.set_custom_response_type(custom_response_type); } diff --git a/graphql_client_cli/src/main.rs b/graphql_client_cli/src/main.rs index 6721953b..a7476477 100644 --- a/graphql_client_cli/src/main.rs +++ b/graphql_client_cli/src/main.rs @@ -139,8 +139,8 @@ fn main() -> CliResult<()> { selected_operation, custom_scalars_module, fragments_other_variant, - external_enums, - custom_variable_types, + external_enums, + custom_variable_types, custom_response_type, } => generate::generate_code(generate::CliCodegenParams { query_path, diff --git a/graphql_client_codegen/src/codegen/inputs.rs b/graphql_client_codegen/src/codegen/inputs.rs index e572b128..ce4f1d43 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -1,4 +1,6 @@ -use super::shared::{field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores}; +use super::shared::{ + field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores, +}; use crate::{ codegen_options::GraphQLClientCodegenOptions, query::{BoundQuery, UsedTypes}, @@ -19,9 +21,12 @@ pub(super) fn generate_input_object_definitions( all_used_types .inputs(query.schema) .map(|(input_id, input)| { - let custom_variable_type = query.query.variables.iter() + let custom_variable_type = query + .query + .variables + .iter() .enumerate() - .find(|(_, v) | v.r#type.id.as_input_id().is_some_and(|i| i == input_id)) + .find(|(_, v)| v.r#type.id.as_input_id().is_some_and(|i| i == input_id)) .map(|(index, _)| custom_variable_types.get(index)) .flatten(); if let Some(custom_type) = custom_variable_type { @@ -61,7 +66,8 @@ fn generate_struct( let struct_name = Ident::new(safe_name.as_ref(), Span::call_site()); let fields = input.fields.iter().map(|(field_name, field_type)| { - let safe_field_name = keyword_replace(to_snake_case_preserve_leading_underscores(field_name)); + let safe_field_name = + keyword_replace(to_snake_case_preserve_leading_underscores(field_name)); let annotation = field_rename_annotation(field_name, safe_field_name.as_ref()); let name_ident = Ident::new(safe_field_name.as_ref(), Span::call_site()); let normalized_field_type_name = options diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 243de0bd..4c473021 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -3,7 +3,9 @@ use crate::{ codegen::{ decorate_type, - shared::{field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores}, + shared::{ + field_rename_annotation, keyword_replace, to_snake_case_preserve_leading_underscores, + }, }, deprecation::DeprecationStrategy, query::{ @@ -12,8 +14,7 @@ use crate::{ }, schema::{Schema, TypeId}, type_qualifiers::GraphqlTypeQualifier, - GraphQLClientCodegenOptions, - GeneralError, + GeneralError, GraphQLClientCodegenOptions, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; @@ -42,12 +43,27 @@ pub(crate) fn render_response_data_fields<'a>( if let Some(custom_response_type) = options.custom_response_type() { if operation.selection_set.len() == 1 { let selection_id = operation.selection_set[0]; - let selection_field = query.query.get_selection(selection_id).as_selected_field() - .ok_or_else(|| GeneralError(format!("Custom response type {custom_response_type} will only work on fields")))?; - calculate_custom_response_type_selection(&mut expanded_selection, response_data_type_id, custom_response_type, selection_id, selection_field); + let selection_field = query + .query + .get_selection(selection_id) + .as_selected_field() + .ok_or_else(|| { + GeneralError(format!( + "Custom response type {custom_response_type} will only work on fields" + )) + })?; + calculate_custom_response_type_selection( + &mut expanded_selection, + response_data_type_id, + custom_response_type, + selection_id, + selection_field, + ); return Ok(expanded_selection); } else { - return Err(GeneralError(format!("Custom response type {custom_response_type} requires single selection field"))); + return Err(GeneralError(format!( + "Custom response type {custom_response_type} requires single selection field" + ))); } } @@ -67,8 +83,8 @@ fn calculate_custom_response_type_selection<'a>( struct_id: ResponseTypeId, custom_response_type: &'a String, selection_id: SelectionId, - field: &'a SelectedField) -{ + field: &'a SelectedField, +) { let (graphql_name, rust_name) = context.field_name(field); let struct_name_string = full_path_prefix(selection_id, context.query); let field = context.query.schema.get_field(field.field_id); @@ -280,7 +296,10 @@ fn calculate_selection<'a>( field_type_qualifiers: &[GraphqlTypeQualifier::Required], flatten: true, graphql_name: None, - rust_name: to_snake_case_preserve_leading_underscores(&fragment.name).into(), + rust_name: to_snake_case_preserve_leading_underscores( + &fragment.name, + ) + .into(), struct_id, deprecation: None, boxed: fragment_is_recursive(*fragment_id, context.query.query), @@ -394,7 +413,8 @@ fn calculate_selection<'a>( continue; } - let original_field_name = to_snake_case_preserve_leading_underscores(&fragment.name); + let original_field_name = + to_snake_case_preserve_leading_underscores(&fragment.name); let final_field_name = keyword_replace(original_field_name); context.push_field(ExpandedField { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 717cacbc..9bc03883 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -57,7 +57,10 @@ impl GeneratedModule<'_> { /// Generate the module and all the code inside. pub(crate) fn to_token_stream(&self) -> Result { - let module_name = Ident::new(&to_snake_case_preserve_leading_underscores(self.operation), Span::call_site()); + let module_name = Ident::new( + &to_snake_case_preserve_leading_underscores(self.operation), + Span::call_site(), + ); let module_visibility = &self.options.module_visibility(); let operation_name = self.operation; let operation_name_ident = self.options.normalization().operation(self.operation); diff --git a/graphql_client_codegen/src/tests/mod.rs b/graphql_client_codegen/src/tests/mod.rs index aaed3e5d..5bd158f9 100644 --- a/graphql_client_codegen/src/tests/mod.rs +++ b/graphql_client_codegen/src/tests/mod.rs @@ -62,7 +62,8 @@ fn blended_custom_types_works() { match r { Ok(_) => { // Variables and returns should be replaced with custom types - assert!(generated_code.contains("pub type SearchQuerySearch = external_crate :: Transaction")); + assert!(generated_code + .contains("pub type SearchQuerySearch = external_crate :: Transaction")); assert!(generated_code.contains("pub type extern_ = external_crate :: ID")); } Err(e) => { diff --git a/graphql_query_derive/src/lib.rs b/graphql_query_derive/src/lib.rs index c6a7eca3..e1314f16 100644 --- a/graphql_query_derive/src/lib.rs +++ b/graphql_query_derive/src/lib.rs @@ -106,7 +106,7 @@ fn build_graphql_client_derive_options( if let Some(custom_variable_types) = custom_variable_types { options.set_custom_variable_types(custom_variable_types); } - + if let Some(custom_response_type) = custom_response_type { options.set_custom_response_type(custom_response_type); }