Skip to content

Commit 97b5ed5

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

File tree

4 files changed

+110
-9
lines changed

4 files changed

+110
-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

+59-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use cynic::{Operation, OperationBuilder};
12
use serde_json::json;
23

3-
#[derive(cynic::QueryVariables)]
4+
#[derive(cynic::QueryVariables, cynic::QueryVariableLiterals)]
45
struct TestArgs<'a> {
56
#[cynic(skip_serializing_if = "Option::is_none")]
67
a_str: Option<&'a str>,
@@ -54,6 +55,63 @@ fn test_unused_variables_not_rendered() {
5455
"###);
5556
}
5657

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

0 commit comments

Comments
 (0)