diff --git a/graphql_client/src/lib.rs b/graphql_client/src/lib.rs index 67364730..07552444 100644 --- a/graphql_client/src/lib.rs +++ b/graphql_client/src/lib.rs @@ -86,6 +86,8 @@ pub trait GraphQLQuery { /// Produce a GraphQL query struct that can be JSON serialized and sent to a GraphQL API. fn build_query(variables: Self::Variables) -> QueryBody; + /// Produce a GraphQL batch query struct that can be JSON serialized and sent to a GraphQL API. + fn build_batch_query(variables: Vec) -> Vec>; } /// The form in which queries are sent over HTTP in most implementations. This will be built using the [`GraphQLQuery`] trait normally. diff --git a/graphql_client/src/reqwest.rs b/graphql_client/src/reqwest.rs index 3541915e..8e23a131 100644 --- a/graphql_client/src/reqwest.rs +++ b/graphql_client/src/reqwest.rs @@ -16,6 +16,19 @@ pub async fn post_graphql( reqwest_response.json().await } +/// Use the provided reqwest::Client to post a GraphQL request. +#[cfg(any(feature = "reqwest", feature = "reqwest-rustls"))] +pub async fn post_graphql_batch( + client: &reqwest::Client, + url: U, + variables: Vec, +) -> Result>, reqwest::Error> { + let body = Q::build_batch_query(variables); + let reqwest_response = client.post(url).json(&body).send().await?; + + reqwest_response.json().await +} + /// Use the provided reqwest::Client to post a GraphQL request. #[cfg(feature = "reqwest-blocking")] pub fn post_graphql_blocking( @@ -28,3 +41,16 @@ pub fn post_graphql_blocking( reqwest_response.json() } + +/// Use the provided reqwest::Client to post a GraphQL request. +#[cfg(feature = "reqwest-blocking")] +pub fn post_graphql_blocking_batch( + client: &reqwest::blocking::Client, + url: U, + variables: Vec, +) -> Result>, reqwest::Error> { + let body = Q::build_batch_query(variables); + let reqwest_response = client.post(url).json(&body).send()?; + + reqwest_response.json() +} diff --git a/graphql_client/tests/batch_queries.rs b/graphql_client/tests/batch_queries.rs new file mode 100644 index 00000000..6005c55f --- /dev/null +++ b/graphql_client/tests/batch_queries.rs @@ -0,0 +1,28 @@ +use graphql_client::*; + +#[derive(GraphQLQuery)] +#[graphql( + query_path = "tests/operation_selection/queries.graphql", + schema_path = "tests/operation_selection/schema.graphql", + response_derives = "Debug, PartialEq, Eq" +)] +pub struct Echo; + +#[test] +fn batch_query() { + let echo_variables = vec![ + echo::Variables { + msg: Some("hi".to_string()), + }, + echo::Variables { + msg: Some("hello".to_string()), + }, + ]; + let echo_batch_queries: serde_json::Value = + serde_json::to_value(Echo::build_batch_query(echo_variables)) + .expect("Failed to serialize the query!"); + assert_eq!( + echo_batch_queries.to_string(), + r#"[{"operationName":"Echo","query":"query Heights($buildingId: ID!, $mountainName: String) {\n mountainHeight(name: $mountainName)\n buildingHeight(id: $buildingId)\n}\n\nquery Echo($msg: String) {\n echo(msg: $msg)\n}\n","variables":{"msg":"hi"}},{"operationName":"Echo","query":"query Heights($buildingId: ID!, $mountainName: String) {\n mountainHeight(name: $mountainName)\n buildingHeight(id: $buildingId)\n}\n\nquery Echo($msg: String) {\n echo(msg: $msg)\n}\n","variables":{"msg":"hello"}}]"# + ); +} 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 d8cc1080..f0470c9e 100644 --- a/graphql_client_codegen/src/codegen/inputs.rs +++ b/graphql_client_codegen/src/codegen/inputs.rs @@ -19,11 +19,16 @@ 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)) - .map(|(index, _)| custom_variable_types.get(index)) - .flatten(); + .find(|(_, v)| match v.r#type.id.as_input_id() { + Some(i) => i == input_id, + None => false, + }) + .and_then(|(index, _)| custom_variable_types.get(index)); if let Some(custom_type) = custom_variable_type { generate_type_def(input, options, custom_type) } else if input.is_one_of { @@ -38,7 +43,7 @@ pub(super) fn generate_input_object_definitions( fn generate_type_def( input: &StoredInputType, options: &GraphQLClientCodegenOptions, - custom_type: &String, + custom_type: &str, ) -> TokenStream { let custom_type = syn::parse_str::(custom_type).unwrap(); let normalized_name = options.normalization().input_name(input.name.as_str()); diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index ec1703b8..9a1fa0fe 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -12,8 +12,7 @@ use crate::{ }, schema::{Schema, TypeId}, type_qualifiers::GraphqlTypeQualifier, - GraphQLClientCodegenOptions, - GeneralError, + GeneralError, GraphQLClientCodegenOptions, }; use heck::*; use proc_macro2::{Ident, Span, TokenStream}; @@ -43,12 +42,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" + ))); } } @@ -66,10 +80,10 @@ pub(crate) fn render_response_data_fields<'a>( fn calculate_custom_response_type_selection<'a>( context: &mut ExpandedSelection<'a>, struct_id: ResponseTypeId, - custom_response_type: &'a String, + custom_response_type: &'a str, 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); @@ -88,7 +102,7 @@ fn calculate_custom_response_type_selection<'a>( name: struct_name_string.into(), }); context.push_type_alias(TypeAlias { - name: custom_response_type.as_str(), + name: custom_response_type, struct_id, boxed: false, }); diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index b225d001..48384229 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -112,6 +112,16 @@ impl GeneratedModule<'_> { } } + fn build_batch_query(variables: Vec) -> Vec> { + variables.into_iter().map(|variable| + graphql_client::QueryBody { + variables: variable, + query: #module_name::QUERY, + operation_name: #module_name::OPERATION_NAME, + } + ).collect() + + } } )) } 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); }