Skip to content

Commit

Permalink
feat: add variable inlining to OperationBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
obmarg committed Aug 23, 2024
1 parent a49710c commit 97b5ed5
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 9 deletions.
28 changes: 27 additions & 1 deletion cynic/src/operation/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::HashSet, marker::PhantomData};
use crate::{
queries::{build_executable_document, OperationType},
schema::{MutationRoot, QueryRoot, SubscriptionRoot},
QueryFragment, QueryVariables,
QueryFragment, QueryVariableLiterals, QueryVariables,
};

use super::Operation;
Expand Down Expand Up @@ -104,12 +104,38 @@ where
self.operation_kind,
self.operation_name.as_deref(),
self.features.clone(),
None,
),
variables: self.variables.ok_or(OperationBuildError::VariablesNotSet)?,
operation_name: self.operation_name,
phantom: PhantomData,
})
}

/// Builds an Operation with all of the current variables inlined into the executable document
/// as arguments.
///
/// You should derive `QueryVariableLiterals` on your `QueryArguments` struct to use this
/// functionality.
pub fn build_with_variables_inlined(
self,
) -> Result<super::Operation<Fragment, ()>, OperationBuildError>
where
Variables: QueryVariableLiterals,
{
let variables = self.variables.ok_or(OperationBuildError::VariablesNotSet)?;
Ok(Operation {
query: build_executable_document::<Fragment, Variables>(
self.operation_kind,
self.operation_name.as_deref(),
self.features.clone(),
Some(&variables),
),
variables: (),
operation_name: self.operation_name,
phantom: PhantomData,
})
}
}

#[derive(thiserror::Error, Debug)]
Expand Down
27 changes: 20 additions & 7 deletions cynic/src/queries/builders.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashSet, marker::PhantomData, sync::mpsc::Sender};

use crate::{coercions::CoercesTo, schema, variables::VariableDefinition};
use crate::{coercions::CoercesTo, schema, variables::VariableDefinition, QueryVariableLiterals};

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

Expand Down Expand Up @@ -43,6 +43,7 @@ impl<'a, SchemaType, VariablesFields> SelectionBuilder<'a, SchemaType, Variables
selection_set: &'a mut SelectionSet,
variables_used: &'a Sender<&'static str>,
features_enabled: &'a HashSet<String>,
inline_variables: Option<&'a dyn QueryVariableLiterals>,
) -> Self {
SelectionBuilder::private_new(
selection_set,
Expand All @@ -51,6 +52,7 @@ impl<'a, SchemaType, VariablesFields> SelectionBuilder<'a, SchemaType, Variables
overall_depth: 0,
features_enabled,
variables_used,
inline_variables,
},
)
}
Expand Down Expand Up @@ -273,12 +275,22 @@ impl<'a, SchemaType, VariablesFields> InputBuilder<'a, SchemaType, VariablesFiel
where
Type: CoercesTo<SchemaType>,
{
self.context
.variables_used
.send(def.name)
.expect("the variables_used channel to be open");

self.destination.push(InputLiteral::Variable(def.name));
match &self.context.inline_variables {
None => {
self.context
.variables_used
.send(def.name)
.expect("the variables_used channel to be open");

self.destination.push(InputLiteral::Variable(def.name));
}
Some(variables) => {
// If the variable returns None we assume it hit a skip_serializing_if and skip it
if let Some(literal) = variables.get(def.name) {
self.destination.push(literal);
}
}
}
}
}

Expand Down Expand Up @@ -443,6 +455,7 @@ struct BuilderContext<'a> {
variables_used: &'a Sender<&'static str>,
recurse_depth: Option<u8>,
overall_depth: u16,
inline_variables: Option<&'a dyn QueryVariableLiterals>,
}

impl BuilderContext<'_> {
Expand Down
4 changes: 4 additions & 0 deletions cynic/src/queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod variables;

use variables::VariableDefinitions;

use crate::QueryVariableLiterals;

pub use self::{
ast::{Argument, InputLiteral, SelectionSet},
builders::{SelectionBuilder, VariableMatch},
Expand All @@ -36,6 +38,7 @@ pub fn build_executable_document<Fragment, Variables>(
r#type: OperationType,
operation_name: Option<&str>,
features_enabled: HashSet<String>,
inline_variables: Option<&dyn QueryVariableLiterals>,
) -> String
where
Fragment: crate::QueryFragment,
Expand All @@ -48,6 +51,7 @@ where
&mut selection_set,
&variable_tx,
&features_enabled,
inline_variables,
);

Fragment::query(builder);
Expand Down
60 changes: 59 additions & 1 deletion cynic/tests/variables.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use cynic::{Operation, OperationBuilder};
use serde_json::json;

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

mod variable_inlining {
use cynic::OperationBuilder;

use super::{schema, TestArgs, TestArgsFields};

#[derive(cynic::QueryFragment, PartialEq, Debug)]
#[cynic(schema_path = "../schemas/simple.graphql", variables = "TestArgs")]
struct TestStruct {
#[arguments(x: 1, y: $a_str)]
field_one: String,
}

#[derive(cynic::QueryFragment, PartialEq, Debug)]
#[cynic(
schema_path = "../schemas/simple.graphql",
graphql_type = "Query",
variables = "TestArgs"
)]
struct QueryWithArguments {
test_struct: Option<TestStruct>,
}

#[test]
fn test_variable_inlining() {
let operation = OperationBuilder::<QueryWithArguments, TestArgs<'_>>::query()
.with_variables(TestArgs {
a_str: Some("boom, this is interpolated"),
})
.build_with_variables_inlined()
.unwrap();

insta::assert_display_snapshot!(operation.query, @r###"
query QueryWithArguments {
testStruct {
fieldOne(x: 1)
}
}
"###);
}

#[test]
fn test_skip_serializing_if_none_for_inlines() {
let operation = OperationBuilder::<QueryWithArguments, TestArgs<'_>>::query()
.with_variables(TestArgs { a_str: None })
.build_with_variables_inlined()
.unwrap();

insta::assert_display_snapshot!(operation.query, @r###"
query QueryWithArguments {
testStruct {
fieldOne(x: 1)
}
}
"###);
}
}

mod schema {
cynic::use_schema!("../schemas/simple.graphql");
}

0 comments on commit 97b5ed5

Please sign in to comment.