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: 2 additions & 0 deletions graphql_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self::Variables>;
/// Produce a GraphQL batch query struct that can be JSON serialized and sent to a GraphQL API.
fn build_batch_query(variables: Vec<Self::Variables>) -> Vec<QueryBody<Self::Variables>>;
}

/// The form in which queries are sent over HTTP in most implementations. This will be built using the [`GraphQLQuery`] trait normally.
Expand Down
26 changes: 26 additions & 0 deletions graphql_client/src/reqwest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ pub async fn post_graphql<Q: GraphQLQuery, U: reqwest::IntoUrl>(
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<Q: GraphQLQuery, U: reqwest::IntoUrl>(
client: &reqwest::Client,
url: U,
variables: Vec<Q::Variables>,
) -> Result<Vec<crate::Response<Q::ResponseData>>, 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<Q: GraphQLQuery, U: reqwest::IntoUrl>(
Expand All @@ -28,3 +41,16 @@ pub fn post_graphql_blocking<Q: GraphQLQuery, U: reqwest::IntoUrl>(

reqwest_response.json()
}

/// Use the provided reqwest::Client to post a GraphQL request.
#[cfg(feature = "reqwest-blocking")]
pub fn post_graphql_blocking_batch<Q: GraphQLQuery, U: reqwest::IntoUrl>(
client: &reqwest::blocking::Client,
url: U,
variables: Vec<Q::Variables>,
) -> Result<Vec<crate::Response<Q::ResponseData>>, reqwest::Error> {
let body = Q::build_batch_query(variables);
let reqwest_response = client.post(url).json(&body).send()?;

reqwest_response.json()
}
28 changes: 28 additions & 0 deletions graphql_client/tests/batch_queries.rs
Original file line number Diff line number Diff line change
@@ -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"}}]"#
);
}
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
15 changes: 10 additions & 5 deletions graphql_client_codegen/src/codegen/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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::<syn::Path>(custom_type).unwrap();
let normalized_name = options.normalization().input_name(input.name.as_str());
Expand Down
34 changes: 24 additions & 10 deletions graphql_client_codegen/src/codegen/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ use crate::{
},
schema::{Schema, TypeId},
type_qualifiers::GraphqlTypeQualifier,
GraphQLClientCodegenOptions,
GeneralError,
GeneralError, GraphQLClientCodegenOptions,
};
use heck::*;
use proc_macro2::{Ident, Span, TokenStream};
Expand Down Expand Up @@ -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"
)));
}
}

Expand All @@ -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);
Expand All @@ -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,
});
Expand Down
10 changes: 10 additions & 0 deletions graphql_client_codegen/src/generated_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ impl GeneratedModule<'_> {
}

}
fn build_batch_query(variables: Vec<Self::Variables>) -> Vec<graphql_client::QueryBody<Self::Variables>> {
variables.into_iter().map(|variable|
graphql_client::QueryBody {
variables: variable,
query: #module_name::QUERY,
operation_name: #module_name::OPERATION_NAME,
}
).collect()

}
}
))
}
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