Skip to content

Commit 3727335

Browse files
committed
graphql: Treat missing variables as null values
Instead of immediately reporting an error, treat missing variables as nulls and let the rest of the execution logic deal with nulls
1 parent 570b348 commit 3727335

File tree

2 files changed

+46
-40
lines changed

2 files changed

+46
-40
lines changed

graphql/src/execution/query.rs

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -741,17 +741,17 @@ struct Transform {
741741
}
742742

743743
impl Transform {
744-
fn variable(
745-
&self,
746-
name: &str,
747-
default: Option<r::Value>,
748-
pos: &Pos,
749-
) -> Result<r::Value, QueryExecutionError> {
744+
/// Look up the value of the variable `name`. If the variable is not
745+
/// defined, return `r::Value::Null`
746+
// graphql-bug-compat: use `default` as the value for an undefined
747+
// variable. Once queries are fully validated, all variables will be
748+
// defined
749+
fn variable(&self, name: &str, default: Option<r::Value>) -> r::Value {
750750
self.variables
751751
.get(name)
752752
.map(|value| value.clone())
753753
.or(default)
754-
.ok_or_else(|| QueryExecutionError::MissingVariableError(pos.clone(), name.to_string()))
754+
.unwrap_or(r::Value::Null)
755755
}
756756

757757
/// Interpolate variable references in the arguments `args`
@@ -760,47 +760,42 @@ impl Transform {
760760
args: Vec<(String, q::Value)>,
761761
defaults: &dyn DefaultLookup,
762762
pos: &Pos,
763-
) -> Result<Vec<(String, r::Value)>, QueryExecutionError> {
763+
) -> Vec<(String, r::Value)> {
764764
args.into_iter()
765765
.map(|(name, val)| {
766766
let default = defaults.find(&name);
767-
self.interpolate_value(val, default, pos)
768-
.map(|val| (name, val))
767+
let val = self.interpolate_value(val, default, pos);
768+
(name, val)
769769
})
770770
.collect()
771771
}
772772

773773
/// Turn `value` into an `r::Value` by resolving variable references
774-
fn interpolate_value(
775-
&self,
776-
value: q::Value,
777-
default: Option<r::Value>,
778-
pos: &Pos,
779-
) -> Result<r::Value, QueryExecutionError> {
774+
fn interpolate_value(&self, value: q::Value, default: Option<r::Value>, pos: &Pos) -> r::Value {
780775
match value {
781-
q::Value::Variable(var) => self.variable(&var, default, pos),
782-
q::Value::Int(ref num) => Ok(r::Value::Int(
783-
num.as_i64().expect("q::Value::Int contains an i64"),
784-
)),
785-
q::Value::Float(f) => Ok(r::Value::Float(f)),
786-
q::Value::String(s) => Ok(r::Value::String(s)),
787-
q::Value::Boolean(b) => Ok(r::Value::Boolean(b)),
788-
q::Value::Null => Ok(r::Value::Null),
789-
q::Value::Enum(s) => Ok(r::Value::Enum(s)),
776+
q::Value::Variable(var) => self.variable(&var, default),
777+
q::Value::Int(ref num) => {
778+
r::Value::Int(num.as_i64().expect("q::Value::Int contains an i64"))
779+
}
780+
q::Value::Float(f) => r::Value::Float(f),
781+
q::Value::String(s) => r::Value::String(s),
782+
q::Value::Boolean(b) => r::Value::Boolean(b),
783+
q::Value::Null => r::Value::Null,
784+
q::Value::Enum(s) => r::Value::Enum(s),
790785
q::Value::List(vals) => {
791-
let vals: Vec<_> = vals
786+
let vals = vals
792787
.into_iter()
793788
.map(|val| self.interpolate_value(val, None, pos))
794-
.collect::<Result<Vec<_>, _>>()?;
795-
Ok(r::Value::List(vals))
789+
.collect();
790+
r::Value::List(vals)
796791
}
797792
q::Value::Object(map) => {
798793
let mut rmap = BTreeMap::new();
799794
for (key, value) in map.into_iter() {
800-
let value = self.interpolate_value(value, None, pos)?;
795+
let value = self.interpolate_value(value, None, pos);
801796
rmap.insert(key, value);
802797
}
803-
Ok(r::Value::object(rmap))
798+
r::Value::object(rmap)
804799
}
805800
}
806801
}
@@ -813,7 +808,7 @@ impl Transform {
813808
dirs: Vec<q::Directive>,
814809
defns: &Vec<s::Directive>,
815810
) -> Result<(Vec<a::Directive>, bool), QueryExecutionError> {
816-
let dirs = dirs
811+
let dirs: Vec<_> = dirs
817812
.into_iter()
818813
.map(|dir| {
819814
let q::Directive {
@@ -827,14 +822,14 @@ impl Transform {
827822
.find(|defn| defn.name == name)
828823
.map(|defn| &defn.arguments),
829824
);
830-
self.interpolate_arguments(arguments, &defaults, &position)
831-
.map(|arguments| a::Directive {
832-
name,
833-
position,
834-
arguments,
835-
})
825+
let arguments = self.interpolate_arguments(arguments, &defaults, &position);
826+
a::Directive {
827+
name,
828+
position,
829+
arguments,
830+
}
836831
})
837-
.collect::<Result<Vec<_>, _>>()?;
832+
.collect();
838833
let skip = dirs.iter().any(|dir| dir.skip());
839834
Ok((dirs, skip))
840835
}
@@ -944,7 +939,7 @@ impl Transform {
944939
}
945940

946941
let mut arguments =
947-
self.interpolate_arguments(arguments, &FieldArgumentDefaults(&field_type), &position)?;
942+
self.interpolate_arguments(arguments, &FieldArgumentDefaults(&field_type), &position);
948943
self.coerce_argument_values(&mut arguments, parent_type, &name)?;
949944

950945
let is_leaf_type = self.schema.document().is_leaf_type(&field_type.field_type);

graphql/tests/query.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1489,7 +1489,7 @@ fn leaf_selection_mismatch() {
14891489

14901490
// see: graphql-bug-compat
14911491
#[test]
1492-
fn missing_var_uses_default() {
1492+
fn missing_variable() {
14931493
run_test_sequentially(|store| async move {
14941494
let deployment = setup(store.as_ref());
14951495
let result = execute_query_document(
@@ -1512,6 +1512,17 @@ fn missing_var_uses_default() {
15121512
};
15131513
let data = extract_data!(result).unwrap();
15141514
assert_eq!(exp, data);
1515+
1516+
let result = execute_query_document(
1517+
&deployment.hash,
1518+
// '$where' is not defined but nullable, ignore the argument
1519+
graphql_parser::parse_query("query { musicians(where: $where) { id } }")
1520+
.expect("invalid test query")
1521+
.into_static(),
1522+
)
1523+
.await;
1524+
let data = extract_data!(result).unwrap();
1525+
assert_eq!(exp, data);
15151526
})
15161527
}
15171528

0 commit comments

Comments
 (0)