Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion graphql_client/tests/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ fn leading_underscores_are_preserved() {
let deserialized: graphql_client::Response<introspection_query::ResponseData> =
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());
}
8 changes: 5 additions & 3 deletions graphql_client_cli/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions graphql_client_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions graphql_client_codegen/src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
mod enums;
mod inputs;
mod selection;
mod shared;
pub(crate) mod shared;

use crate::{
query::*,
schema::{InputId, TypeId},
type_qualifiers::GraphqlTypeQualifier,
GeneralError, GraphQLClientCodegenOptions,
};
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;

/// The main code generation function.
Expand Down Expand Up @@ -139,7 +139,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);
Expand Down
16 changes: 11 additions & 5 deletions graphql_client_codegen/src/codegen/inputs.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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},
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};

Expand All @@ -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 {
Expand Down Expand Up @@ -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(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
Expand Down
45 changes: 32 additions & 13 deletions graphql_client_codegen/src/codegen/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
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::{
Expand All @@ -12,10 +14,8 @@ use crate::{
},
schema::{Schema, TypeId},
type_qualifiers::GraphqlTypeQualifier,
GraphQLClientCodegenOptions,
GeneralError,
GeneralError, GraphQLClientCodegenOptions,
};
use heck::*;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::borrow::Cow;
Expand Down Expand Up @@ -43,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"
)));
}
}

Expand All @@ -68,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);
Expand Down Expand Up @@ -281,7 +296,10 @@ 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),
Expand Down Expand Up @@ -395,7 +413,8 @@ 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 {
Expand Down Expand Up @@ -578,7 +597,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)
Expand Down
14 changes: 14 additions & 0 deletions graphql_client_codegen/src/codegen/shared.rs
Original file line number Diff line number Diff line change
@@ -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] = &[
Expand Down
7 changes: 5 additions & 2 deletions graphql_client_codegen/src/generated_module.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{
codegen::shared::to_snake_case_preserve_leading_underscores,
codegen_options::*,
query::{BoundQuery, OperationId},
BoxError,
};
use heck::*;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::{error::Error, fmt::Display};
Expand Down Expand Up @@ -57,7 +57,10 @@ impl GeneratedModule<'_> {

/// Generate the module and all the code inside.
pub(crate) fn to_token_stream(&self) -> Result<TokenStream, BoxError> {
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);
Expand Down
3 changes: 2 additions & 1 deletion graphql_client_codegen/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion graphql_query_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down