Skip to content

Commit bc71a86

Browse files
committed
feat: add variable inlining to OperationBuilder
1 parent a49710c commit bc71a86

File tree

4 files changed

+109
-9
lines changed

4 files changed

+109
-9
lines changed

cynic/src/operation/builder.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashSet, marker::PhantomData};
33
use crate::{
44
queries::{build_executable_document, OperationType},
55
schema::{MutationRoot, QueryRoot, SubscriptionRoot},
6-
QueryFragment, QueryVariables,
6+
QueryFragment, QueryVariableLiterals, QueryVariables,
77
};
88

99
use super::Operation;
@@ -104,12 +104,38 @@ where
104104
self.operation_kind,
105105
self.operation_name.as_deref(),
106106
self.features.clone(),
107+
None,
107108
),
108109
variables: self.variables.ok_or(OperationBuildError::VariablesNotSet)?,
109110
operation_name: self.operation_name,
110111
phantom: PhantomData,
111112
})
112113
}
114+
115+
/// Builds an Operation with all of the current variables inlined into the executable document
116+
/// as arguments.
117+
///
118+
/// You should derive `QueryVariableLiterals` on your `QueryArguments` struct to use this
119+
/// functionality.
120+
pub fn build_with_variables_inlined(
121+
self,
122+
) -> Result<super::Operation<Fragment, ()>, OperationBuildError>
123+
where
124+
Variables: QueryVariableLiterals,
125+
{
126+
let variables = self.variables.ok_or(OperationBuildError::VariablesNotSet)?;
127+
Ok(Operation {
128+
query: build_executable_document::<Fragment, Variables>(
129+
self.operation_kind,
130+
self.operation_name.as_deref(),
131+
self.features.clone(),
132+
Some(&variables),
133+
),
134+
variables: (),
135+
operation_name: self.operation_name,
136+
phantom: PhantomData,
137+
})
138+
}
113139
}
114140

115141
#[derive(thiserror::Error, Debug)]

cynic/src/queries/builders.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{borrow::Cow, collections::HashSet, marker::PhantomData, sync::mpsc::Sender};
22

3-
use crate::{coercions::CoercesTo, schema, variables::VariableDefinition};
3+
use crate::{coercions::CoercesTo, schema, variables::VariableDefinition, QueryVariableLiterals};
44

55
use super::{ast::*, to_input_literal, FlattensInto, IsFieldType, Recursable};
66

@@ -43,6 +43,7 @@ impl<'a, SchemaType, VariablesFields> SelectionBuilder<'a, SchemaType, Variables
4343
selection_set: &'a mut SelectionSet,
4444
variables_used: &'a Sender<&'static str>,
4545
features_enabled: &'a HashSet<String>,
46+
inline_variables: Option<&'a dyn QueryVariableLiterals>,
4647
) -> Self {
4748
SelectionBuilder::private_new(
4849
selection_set,
@@ -51,6 +52,7 @@ impl<'a, SchemaType, VariablesFields> SelectionBuilder<'a, SchemaType, Variables
5152
overall_depth: 0,
5253
features_enabled,
5354
variables_used,
55+
inline_variables,
5456
},
5557
)
5658
}
@@ -273,12 +275,22 @@ impl<'a, SchemaType, VariablesFields> InputBuilder<'a, SchemaType, VariablesFiel
273275
where
274276
Type: CoercesTo<SchemaType>,
275277
{
276-
self.context
277-
.variables_used
278-
.send(def.name)
279-
.expect("the variables_used channel to be open");
280-
281-
self.destination.push(InputLiteral::Variable(def.name));
278+
match &self.context.inline_variables {
279+
None => {
280+
self.context
281+
.variables_used
282+
.send(def.name)
283+
.expect("the variables_used channel to be open");
284+
285+
self.destination.push(InputLiteral::Variable(def.name));
286+
}
287+
Some(variables) => {
288+
// If the variable returns None we assume it hit a skip_serializing_if and skip it
289+
if let Some(literal) = variables.get(def.name) {
290+
self.destination.push(literal);
291+
}
292+
}
293+
}
282294
}
283295
}
284296

@@ -443,6 +455,7 @@ struct BuilderContext<'a> {
443455
variables_used: &'a Sender<&'static str>,
444456
recurse_depth: Option<u8>,
445457
overall_depth: u16,
458+
inline_variables: Option<&'a dyn QueryVariableLiterals>,
446459
}
447460

448461
impl BuilderContext<'_> {

cynic/src/queries/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ mod variables;
1111

1212
use variables::VariableDefinitions;
1313

14+
use crate::QueryVariableLiterals;
15+
1416
pub use self::{
1517
ast::{Argument, InputLiteral, SelectionSet},
1618
builders::{SelectionBuilder, VariableMatch},
@@ -36,6 +38,7 @@ pub fn build_executable_document<Fragment, Variables>(
3638
r#type: OperationType,
3739
operation_name: Option<&str>,
3840
features_enabled: HashSet<String>,
41+
inline_variables: Option<&dyn QueryVariableLiterals>,
3942
) -> String
4043
where
4144
Fragment: crate::QueryFragment,
@@ -48,6 +51,7 @@ where
4851
&mut selection_set,
4952
&variable_tx,
5053
&features_enabled,
54+
inline_variables,
5155
);
5256

5357
Fragment::query(builder);

cynic/tests/variables.rs

+58-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use serde_json::json;
22

3-
#[derive(cynic::QueryVariables)]
3+
#[derive(cynic::QueryVariables, cynic::QueryVariableLiterals)]
44
struct TestArgs<'a> {
55
#[cynic(skip_serializing_if = "Option::is_none")]
66
a_str: Option<&'a str>,
@@ -54,6 +54,63 @@ fn test_unused_variables_not_rendered() {
5454
"###);
5555
}
5656

57+
mod variable_inlining {
58+
use cynic::OperationBuilder;
59+
60+
use super::{schema, TestArgs, TestArgsFields};
61+
62+
#[derive(cynic::QueryFragment, PartialEq, Debug)]
63+
#[cynic(schema_path = "../schemas/simple.graphql", variables = "TestArgs")]
64+
struct TestStruct {
65+
#[arguments(x: 1, y: $a_str)]
66+
field_one: String,
67+
}
68+
69+
#[derive(cynic::QueryFragment, PartialEq, Debug)]
70+
#[cynic(
71+
schema_path = "../schemas/simple.graphql",
72+
graphql_type = "Query",
73+
variables = "TestArgs"
74+
)]
75+
struct QueryWithArguments {
76+
test_struct: Option<TestStruct>,
77+
}
78+
79+
#[test]
80+
fn test_variable_inlining() {
81+
let operation = OperationBuilder::<QueryWithArguments, TestArgs<'_>>::query()
82+
.with_variables(TestArgs {
83+
a_str: Some("boom, this is interpolated"),
84+
})
85+
.build_with_variables_inlined()
86+
.unwrap();
87+
88+
insta::assert_display_snapshot!(operation.query, @r###"
89+
query QueryWithArguments {
90+
testStruct {
91+
fieldOne(x: 1, y: "boom, this is interpolated")
92+
}
93+
}
94+
"###);
95+
}
96+
97+
#[test]
98+
fn test_skip_serializing_if_none_for_inlines() {
99+
let operation = OperationBuilder::<QueryWithArguments, TestArgs<'_>>::query()
100+
.with_variables(TestArgs { a_str: None })
101+
.build_with_variables_inlined()
102+
.unwrap();
103+
104+
insta::assert_display_snapshot!(operation.query, @r###"
105+
query QueryWithArguments {
106+
testStruct {
107+
fieldOne(x: 1)
108+
}
109+
}
110+
"###);
111+
}
112+
}
113+
57114
mod schema {
58115
cynic::use_schema!("../schemas/simple.graphql");
59116
}

0 commit comments

Comments
 (0)