Skip to content

Commit 4d41bd0

Browse files
committed
Add support for default input object values
It is not clear that this is the right approach: we convert the default values from the query to rust literals for the input objects. The default value is then sent twice: in the query string and in the variable. The reason for using this approach is that Rust structs do not support only setting some fields and not others, so there is no distinction between absent and null fields: there is no way to say if the field in the Variables struct is explicitly set to null or left out (expecting the default value to be used). Maybe we could generate something other than an Option here, but we would have to figure out how to make this ergonomic.
1 parent 74a6583 commit 4d41bd0

File tree

4 files changed

+57
-10
lines changed

4 files changed

+57
-10
lines changed

graphql_query_derive/src/inputs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ mod tests {
125125
"# [ derive ( Debug , Serialize ) ] ",
126126
"# [ serde ( rename_all = \"camelCase\" ) ] ",
127127
"pub struct Cat { ",
128-
"offsprings : Vec < Cat > , ",
129-
"pawsCount : Float , ",
130-
"requirements : Option < CatRequirements > , ",
128+
"pub offsprings : Vec < Cat > , ",
129+
"pub pawsCount : Float , ",
130+
"pub requirements : Option < CatRequirements > , ",
131131
"}",
132132
].into_iter()
133133
.collect();

graphql_query_derive/src/variables.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use field_type::FieldType;
22
use graphql_parser;
33
use proc_macro2::{Ident, Span, TokenStream};
44
use query::QueryContext;
5+
use std::collections::BTreeMap;
56

67
#[derive(Debug)]
78
pub struct Variable {
@@ -16,7 +17,12 @@ impl Variable {
1617
Some(default) => {
1718
let fn_name = Ident::new(&format!("default_{}", self.name), Span::call_site());
1819
let ty = self.ty.to_rust(context, "");
19-
let value = graphql_parser_value_to_literal(default, self.ty.is_optional());
20+
let value = graphql_parser_value_to_literal(
21+
default,
22+
context,
23+
&self.ty,
24+
self.ty.is_optional(),
25+
);
2026
quote! {
2127
pub fn #fn_name() -> #ty {
2228
#value
@@ -41,6 +47,8 @@ impl ::std::convert::From<graphql_parser::query::VariableDefinition> for Variabl
4147

4248
fn graphql_parser_value_to_literal(
4349
value: &graphql_parser::query::Value,
50+
context: &QueryContext,
51+
ty: &FieldType,
4452
is_optional: bool,
4553
) -> TokenStream {
4654
use graphql_parser::query::Value;
@@ -63,14 +71,14 @@ fn graphql_parser_value_to_literal(
6371
Value::List(inner) => {
6472
let elements = inner
6573
.iter()
66-
.map(|val| graphql_parser_value_to_literal(val, false));
74+
.map(|val| graphql_parser_value_to_literal(val, context, ty, false));
6775
quote! {
6876
vec![
6977
#(#elements,)*
7078
]
7179
}
7280
}
73-
Value::Object(_) => unimplemented!("object literal as default for variable"),
81+
Value::Object(obj) => render_object_literal(obj, ty, context),
7482
};
7583

7684
if is_optional {
@@ -79,3 +87,41 @@ fn graphql_parser_value_to_literal(
7987
inner
8088
}
8189
}
90+
91+
fn render_object_literal(
92+
object: &BTreeMap<String, graphql_parser::query::Value>,
93+
ty: &FieldType,
94+
context: &QueryContext,
95+
) -> TokenStream {
96+
let type_name = ty.inner_name_string();
97+
let constructor = Ident::new(&type_name, Span::call_site());
98+
let schema_type = context
99+
.schema
100+
.inputs
101+
.get(&type_name)
102+
.expect("unknown input type");
103+
let fields: Vec<TokenStream> = schema_type
104+
.fields
105+
.iter()
106+
.map(|(name, field)| {
107+
let field_name = Ident::new(&name, Span::call_site());
108+
let provided_value = object.get(name);
109+
match provided_value {
110+
Some(default_value) => {
111+
let value = graphql_parser_value_to_literal(
112+
default_value,
113+
context,
114+
&field.type_,
115+
field.type_.is_optional(),
116+
);
117+
quote!(#field_name: #value)
118+
}
119+
None => quote!(#field_name: None),
120+
}
121+
})
122+
.collect();
123+
124+
quote!(#constructor {
125+
#(#fields,)*
126+
})
127+
}

tests/input_object_variables.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ struct DefaultInputObjectVariablesQuery;
3939
fn input_object_variables_default() {
4040
let variables = default_input_object_variables_query::Variables {
4141
msg: default_input_object_variables_query::Variables::default_msg(),
42-
reps: default_input_object_variables_query::Variables::default_reps(),
4342
};
4443

4544
let out = serde_json::to_string(&variables).unwrap();
4645

47-
assert_eq!(out, r#"{"msg":"o, hai","reps":3}"#);
46+
assert_eq!(out, r#"{"msg":{"content":null,"to":{"category":null,"email":"[email protected]","name":null}}}"#);
4847
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
query VariablesQuery($msg: String = "o, hai", $reps: Int = 3) {
2-
echo(message: $msg, repetitions: $reps) {
1+
query VariablesQuery(
2+
$msg: Message = { to: { email: "[email protected]" } }
3+
) {
4+
echo(message: $msg) {
35
result
46
}
57
}

0 commit comments

Comments
 (0)