Skip to content

Commit df6f35a

Browse files
authored
default variable fixes (#1376)
* Allow default values on non-`Null` variables in `DefaultValuesOfCorrectType` validation rule * Link CHANGELOG entry to PR #1376 * Honor variable default values in `validate_input_values`
1 parent 653d839 commit df6f35a

5 files changed

Lines changed: 133 additions & 32 deletions

File tree

juniper/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
6464
- Missing `@specifiedBy(url:)` directive in [SDL] generated by `RootNode::as_sdl()` and `RootNode::as_document()` methods. ([#1348])
6565
- Incorrect double escaping in `ScalarToken::String` `Display`ing. ([#1349])
6666
- Memory leak caused by incorrect error handling in `#[graphql_subscription]` macro expansion. ([#1371])
67+
- Incorrect rejection of default values on non-`Null` variables. ([#1376])
6768

6869
[#864]: /../../issues/864
6970
[#1055]: /../../issues/1055
@@ -78,6 +79,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
7879
[#1358]: /../../pull/1358
7980
[#1361]: /../../pull/1361
8081
[#1371]: /../../pull/1371
82+
[#1376]: /../../pull/1376
8183
[graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525
8284
[graphql/graphql-spec#687]: https://github.com/graphql/graphql-spec/issues/687
8385
[graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805

juniper/src/executor_tests/variables.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,61 @@ async fn allow_non_nullable_inputs_to_be_set_to_value_directly() {
575575
.await;
576576
}
577577

578+
#[tokio::test]
579+
async fn default_used_for_non_nullable_variable_when_not_provided() {
580+
run_variable_query(
581+
r#"query q($value: String! = "fallback") { fieldWithNonNullableStringInput(input: $value) }"#,
582+
graphql::vars! {},
583+
|result| {
584+
assert_eq!(
585+
result.get_field_value("fieldWithNonNullableStringInput"),
586+
Some(&graphql::value!(r#""fallback""#)),
587+
);
588+
},
589+
)
590+
.await;
591+
}
592+
593+
#[tokio::test]
594+
async fn provided_value_overrides_non_nullable_variable_default() {
595+
run_variable_query(
596+
r#"query q($value: String! = "fallback") { fieldWithNonNullableStringInput(input: $value) }"#,
597+
graphql::vars! {"value": "override"},
598+
|result| {
599+
assert_eq!(
600+
result.get_field_value("fieldWithNonNullableStringInput"),
601+
Some(&graphql::value!(r#""override""#)),
602+
);
603+
},
604+
)
605+
.await;
606+
}
607+
608+
#[tokio::test]
609+
async fn does_not_allow_null_for_non_nullable_variable_with_default() {
610+
let schema = RootNode::new(
611+
TestType,
612+
EmptyMutation::<()>::new(),
613+
EmptySubscription::<()>::new(),
614+
);
615+
616+
let query = r#"query q($value: String! = "fallback") { fieldWithNonNullableStringInput(input: $value) }"#;
617+
let vars = graphql::vars! {"value": null};
618+
619+
let error = crate::execute(query, None, &schema, &vars, &())
620+
.await
621+
.unwrap_err();
622+
623+
assert_eq!(
624+
error,
625+
RuleError::new(
626+
r#"Variable "$value" of required type "String!" was not provided."#,
627+
&[SourcePosition::new(8, 0, 8)],
628+
)
629+
.into(),
630+
);
631+
}
632+
578633
#[tokio::test]
579634
async fn allow_lists_to_be_null() {
580635
run_variable_query(

juniper/src/validation/input_value.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ fn validate_var_defs<S>(
5555
let raw_type_name = def.var_type.item.innermost_name();
5656
match schema.concrete_type_by_name(raw_type_name) {
5757
Some(t) if t.is_input() => {
58+
// Spec §6.1.2: if no value is provided and a default value
59+
// exists, the default value is used regardless of nullability.
60+
if values.get(name.item).is_none() && def.default_value.is_some() {
61+
continue;
62+
}
63+
5864
let ct = schema.make_type(&def.var_type.item);
5965

6066
if def.var_type.item.is_non_null() && is_absent_or_null(values.get(name.item)) {

juniper/src/validation/rules/default_values_of_correct_type.rs

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,13 @@ where
2828
ref span,
2929
}) = var_def.default_value
3030
{
31-
if var_def.var_type.item.is_non_null() {
31+
let meta_type = ctx.schema.make_type(&var_def.var_type.item);
32+
33+
if let Some(err) = validate_literal_value(ctx.schema, &meta_type, var_value) {
3234
ctx.report_error(
33-
&non_null_error_message(var_name.item, &var_def.var_type.item),
35+
&type_error_message(var_name.item, &var_def.var_type.item, err),
3436
&[span.start],
35-
)
36-
} else {
37-
let meta_type = ctx.schema.make_type(&var_def.var_type.item);
38-
39-
if let Some(err) = validate_literal_value(ctx.schema, &meta_type, var_value) {
40-
ctx.report_error(
41-
&type_error_message(var_name.item, &var_def.var_type.item, err),
42-
&[span.start],
43-
);
44-
}
37+
);
4538
}
4639
}
4740
}
@@ -58,16 +51,9 @@ fn type_error_message(
5851
)
5952
}
6053

61-
fn non_null_error_message(arg_name: impl fmt::Display, type_name: impl fmt::Display) -> String {
62-
format!(
63-
"Argument \"{arg_name}\" has type \"{type_name}\" and is not nullable, \
64-
so it can't have a default value",
65-
)
66-
}
67-
6854
#[cfg(test)]
6955
mod tests {
70-
use super::{factory, non_null_error_message, type_error_message};
56+
use super::{factory, type_error_message};
7157

7258
use crate::{
7359
parser::SourcePosition,
@@ -117,24 +103,30 @@ mod tests {
117103
}
118104

119105
#[test]
120-
fn no_required_variables_with_default_values() {
106+
fn required_variables_with_valid_default_values() {
107+
expect_passes_rule::<_, _, DefaultScalarValue>(
108+
factory,
109+
r#"
110+
query RequiredWithDefaults($a: Int! = 3, $b: String! = "default") {
111+
dog { name }
112+
}
113+
"#,
114+
);
115+
}
116+
117+
#[test]
118+
fn required_variables_with_null_default_value() {
121119
expect_fails_rule::<_, _, DefaultScalarValue>(
122120
factory,
123121
r#"
124-
query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {
122+
query NullDefaultForRequired($a: Int! = null) {
125123
dog { name }
126124
}
127125
"#,
128-
&[
129-
RuleError::new(
130-
&non_null_error_message("a", "Int!"),
131-
&[SourcePosition::new(55, 1, 54)],
132-
),
133-
RuleError::new(
134-
&non_null_error_message("b", "String!"),
135-
&[SourcePosition::new(72, 1, 71)],
136-
),
137-
],
126+
&[RuleError::new(
127+
&type_error_message("a", "Int!", error::non_null("Int!")),
128+
&[SourcePosition::new(53, 1, 52)],
129+
)],
138130
);
139131
}
140132

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! Checks that non-`Null` variables may carry a default value, per [§6.1.2]
2+
//! of the GraphQL spec.
3+
//! See [#1376](https://github.com/graphql-rust/juniper/pull/1376) for details.
4+
//!
5+
//! [§6.1.2]: https://spec.graphql.org/October2021/#sec-Coercing-Variable-Values
6+
7+
use juniper::{
8+
EmptyMutation, EmptySubscription, RootNode, graphql_object, graphql_value, graphql_vars,
9+
};
10+
11+
pub struct Query;
12+
13+
#[graphql_object]
14+
impl Query {
15+
fn hello() -> &'static str {
16+
"world"
17+
}
18+
}
19+
20+
type Schema = RootNode<Query, EmptyMutation, EmptySubscription>;
21+
22+
const QUERY: &str = r#"
23+
query ($var: Boolean! = true) {
24+
__typename @skip(if: $var)
25+
}
26+
"#;
27+
28+
#[tokio::test]
29+
async fn default_applies_when_variable_not_provided() {
30+
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
31+
32+
assert_eq!(
33+
juniper::execute(QUERY, None, &schema, &graphql_vars! {}, &()).await,
34+
Ok((graphql_value!({}), vec![])),
35+
);
36+
}
37+
38+
#[tokio::test]
39+
async fn provided_variable_overrides_default() {
40+
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
41+
42+
assert_eq!(
43+
juniper::execute(QUERY, None, &schema, &graphql_vars! {"var": false}, &()).await,
44+
Ok((graphql_value!({"__typename": "Query"}), vec![])),
45+
);
46+
}

0 commit comments

Comments
 (0)