diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 4dc03f95d..26939bad8 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -54,6 +54,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi - Upgraded [GraphiQL] to 3.0.9 version (requires new [`graphql-transport-ws` GraphQL over WebSocket Protocol] integration on server, see `juniper_warp/examples/subscription.rs`). ([#1188], [#1193], [#1204]) - Made `LookAheadMethods::children()` method to return slice instead of `Vec`. ([#1200]) - Abstracted `Spanning::start` and `Spanning::end` fields into separate struct `Span`. ([#1207], [#1208]) +- Added `Span` to `Arguments` and `LookAheadArguments`. ([#1206], [#1209]) ### Added @@ -132,8 +133,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi [#1199]: /../../pull/1199 [#1200]: /../../pull/1200 [#1204]: /../../pull/1204 +[#1206]: /../../pull/1206 [#1207]: /../../pull/1207 [#1208]: /../../pull/1208 +[#1209]: /../../pull/1209 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083 [CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j diff --git a/juniper/src/executor/look_ahead.rs b/juniper/src/executor/look_ahead.rs index 54643db7c..3fe73c35e 100644 --- a/juniper/src/executor/look_ahead.rs +++ b/juniper/src/executor/look_ahead.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ ast::{Directive, Fragment, InputValue, Selection}, - parser::Spanning, + parser::{Span, Spanning}, value::ScalarValue, }; @@ -18,48 +18,71 @@ pub enum Applies<'a> { OnlyType(&'a str), } -/// A JSON-like value that can is used as argument in the query execution +/// Shortcut for a [`Spanning`] containing a borrowed [`Span`]. +type BorrowedSpanning<'a, T> = Spanning; + +/// JSON-like value that can be used as an argument in the query execution. /// -/// In contrast to `InputValue` these values do only contain constants, +/// In contrast to an [`InputValue`], these values do only contain constants, /// meaning that variables are already resolved. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[allow(missing_docs)] pub enum LookAheadValue<'a, S: 'a> { Null, Scalar(&'a S), Enum(&'a str), - List(Vec>), - Object(Vec<(&'a str, LookAheadValue<'a, S>)>), + List(Vec>>), + Object( + Vec<( + BorrowedSpanning<'a, &'a str>, + BorrowedSpanning<'a, LookAheadValue<'a, S>>, + )>, + ), } impl<'a, S> LookAheadValue<'a, S> where S: ScalarValue, { - fn from_input_value(input_value: &'a InputValue, vars: &'a Variables) -> Self { - match *input_value { - InputValue::Null => LookAheadValue::Null, - InputValue::Scalar(ref s) => LookAheadValue::Scalar(s), - InputValue::Enum(ref e) => LookAheadValue::Enum(e), - InputValue::Variable(ref name) => vars - .get(name) - .map(|v| Self::from_input_value(v, vars)) - .unwrap_or(LookAheadValue::Null), - InputValue::List(ref l) => LookAheadValue::List( - l.iter() - .map(|i| LookAheadValue::from_input_value(&i.item, vars)) - .collect(), - ), - InputValue::Object(ref o) => LookAheadValue::Object( - o.iter() - .map(|(n, i)| { - ( - &n.item as &str, - LookAheadValue::from_input_value(&i.item, vars), - ) + fn from_input_value( + input_value: BorrowedSpanning<'a, &'a InputValue>, + vars: &'a Variables, + ) -> BorrowedSpanning<'a, Self> { + Spanning { + span: input_value.span, + item: match input_value.item { + InputValue::Null => Self::Null, + InputValue::Scalar(s) => Self::Scalar(s), + InputValue::Enum(e) => Self::Enum(e), + InputValue::Variable(name) => vars + .get(name) + .map(|item| { + let input_value = Spanning { + span: input_value.span, + item, + }; + Self::from_input_value(input_value, vars).item }) - .collect(), - ), + .unwrap_or(Self::Null), + InputValue::List(l) => Self::List( + l.iter() + .map(|i| Self::from_input_value(i.as_ref(), vars)) + .collect(), + ), + InputValue::Object(o) => Self::Object( + o.iter() + .map(|(n, i)| { + ( + Spanning { + span: &n.span, + item: n.item.as_str(), + }, + Self::from_input_value(i.as_ref(), vars), + ) + }) + .collect(), + ), + }, } } } @@ -68,7 +91,7 @@ where #[derive(Debug, Clone, PartialEq)] pub struct LookAheadArgument<'a, S: 'a> { name: &'a str, - value: LookAheadValue<'a, S>, + value: BorrowedSpanning<'a, LookAheadValue<'a, S>>, } impl<'a, S> LookAheadArgument<'a, S> @@ -81,7 +104,7 @@ where ) -> Self { LookAheadArgument { name: name.item, - value: LookAheadValue::from_input_value(&value.item, vars), + value: LookAheadValue::from_input_value(value.as_ref(), vars), } } @@ -92,7 +115,12 @@ where /// The value of the argument pub fn value(&'a self) -> &LookAheadValue<'a, S> { - &self.value + &self.value.item + } + + /// The input source span of the argument + pub fn span(&self) -> &Span { + self.value.span } } @@ -145,7 +173,7 @@ where .find(|item| item.0.item == "if") .map(|(_, v)| { if let LookAheadValue::Scalar(s) = - LookAheadValue::from_input_value(&v.item, vars) + LookAheadValue::from_input_value(v.as_ref(), vars).item { s.as_bool().unwrap_or(false) } else { @@ -160,7 +188,7 @@ where .find(|item| item.0.item == "if") .map(|(_, v)| { if let LookAheadValue::Scalar(b) = - LookAheadValue::from_input_value(&v.item, vars) + LookAheadValue::from_input_value(v.as_ref(), vars).item { b.as_bool().map(::std::ops::Not::not).unwrap_or(false) } else { @@ -472,12 +500,12 @@ impl<'a, S> LookAheadMethods<'a, S> for LookAheadSelection<'a, S> { #[cfg(test)] mod tests { - use std::collections::HashMap; + use std::{collections::HashMap, ops::Range}; use crate::{ ast::{Document, OwnedDocument}, graphql_vars, - parser::UnlocatedParseResult, + parser::{SourcePosition, UnlocatedParseResult}, schema::model::SchemaType, validation::test_harness::{MutationRoot, QueryRoot, SubscriptionRoot}, value::{DefaultScalarValue, ScalarValue}, @@ -509,6 +537,13 @@ mod tests { fragments } + fn span(range: Range<(usize, usize, usize)>) -> Span { + Span { + start: SourcePosition::new(range.start.0, range.start.1, range.start.2), + end: SourcePosition::new(range.end.0, range.end.1, range.end.2), + } + } + #[test] fn check_simple_query() { let docs = parse_document_source::( @@ -711,12 +746,17 @@ query Hero { &fragments, ) .unwrap(); + let span0 = span((32, 2, 18)..(38, 2, 24)); + let span1 = span((77, 4, 24)..(81, 4, 28)); let expected = LookAheadSelection { name: "hero", alias: None, arguments: vec![LookAheadArgument { name: "episode", - value: LookAheadValue::Enum("EMPIRE"), + value: Spanning { + item: LookAheadValue::Enum("EMPIRE"), + span: &span0, + }, }], applies_for: Applies::All, children: vec![ @@ -732,7 +772,10 @@ query Hero { alias: None, arguments: vec![LookAheadArgument { name: "uppercase", - value: LookAheadValue::Scalar(&DefaultScalarValue::Boolean(true)), + value: Spanning { + item: LookAheadValue::Scalar(&DefaultScalarValue::Boolean(true)), + span: &span1, + }, }], children: Vec::new(), applies_for: Applies::All, @@ -768,12 +811,16 @@ query Hero($episode: Episode) { &fragments, ) .unwrap(); + let span0 = span((51, 2, 18)..(59, 2, 26)); let expected = LookAheadSelection { name: "hero", alias: None, arguments: vec![LookAheadArgument { name: "episode", - value: LookAheadValue::Enum("JEDI"), + value: Spanning { + item: LookAheadValue::Enum("JEDI"), + span: &span0, + }, }], applies_for: Applies::All, children: vec![ @@ -821,12 +868,16 @@ query Hero($episode: Episode) { &fragments, ) .unwrap(); + let span0 = span((51, 2, 18)..(59, 2, 26)); let expected = LookAheadSelection { name: "hero", alias: None, arguments: vec![LookAheadArgument { name: "episode", - value: LookAheadValue::Null, + value: Spanning { + item: LookAheadValue::Null, + span: &span0, + }, }], applies_for: Applies::All, children: vec![LookAheadSelection { @@ -1121,12 +1172,16 @@ fragment comparisonFields on Character { &fragments, ) .unwrap(); + let span0 = span((85, 2, 11)..(88, 2, 14)); let expected = LookAheadSelection { name: "hero", alias: None, arguments: vec![LookAheadArgument { name: "id", - value: LookAheadValue::Scalar(&DefaultScalarValue::Int(42)), + value: Spanning { + item: LookAheadValue::Scalar(&DefaultScalarValue::Int(42)), + span: &span0, + }, }], applies_for: Applies::All, children: vec![ diff --git a/juniper/src/parser/utils.rs b/juniper/src/parser/utils.rs index 3ff0318d5..2494dce6a 100644 --- a/juniper/src/parser/utils.rs +++ b/juniper/src/parser/utils.rs @@ -52,15 +52,15 @@ impl Span { /// Data structure used to wrap items into a [`Span`]. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct Spanning { +pub struct Spanning { /// Wrapped item. pub item: T, /// [`Span`] of the wrapped item. - pub span: Span, + pub span: Sp, } -impl Spanning { +impl Spanning { #[doc(hidden)] pub fn new(span: Span, item: T) -> Self { Self { item, span } @@ -126,6 +126,14 @@ impl Spanning { pub fn and_then Option>(self, f: F) -> Option> { f(self.item).map(|item| Spanning::new(self.span, item)) } + + /// Converts into a [`Spanning`] containing a borrowed item and a borrowed [`Span`]. + pub(crate) fn as_ref(&self) -> Spanning<&'_ T, &'_ Span> { + Spanning { + item: &self.item, + span: &self.span, + } + } } impl fmt::Display for Spanning { diff --git a/juniper/src/types/async_await.rs b/juniper/src/types/async_await.rs index f0988cf3b..df49ac356 100644 --- a/juniper/src/types/async_await.rs +++ b/juniper/src/types/async_await.rs @@ -244,7 +244,8 @@ where m.item .iter() .filter_map(|(k, v)| { - v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) + let val = v.item.clone().into_const(exec_vars)?; + Some((k.item, Spanning::new(v.span, val))) }) .collect() }), diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index c99825091..8392d24a0 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -68,13 +68,13 @@ pub enum TypeKind { /// Field argument container #[derive(Debug)] pub struct Arguments<'a, S = DefaultScalarValue> { - args: Option>>, + args: Option>>>, } impl<'a, S> Arguments<'a, S> { #[doc(hidden)] pub fn new( - mut args: Option>>, + mut args: Option>>>, meta_args: &'a Option>>, ) -> Self where @@ -89,7 +89,7 @@ impl<'a, S> Arguments<'a, S> { let arg_name = arg.name.as_str(); if args.get(arg_name).is_none() { if let Some(val) = arg.default_value.as_ref() { - args.insert(arg_name, val.clone()); + args.insert(arg_name, Spanning::unlocated(val.clone())); } } } @@ -117,10 +117,16 @@ impl<'a, S> Arguments<'a, S> { self.args .as_ref() .and_then(|args| args.get(name)) + .map(|spanning| &spanning.item) .map(InputValue::convert) .transpose() .map_err(IntoFieldError::into_field_error) } + + /// Gets a direct reference to the [`Spanning`] argument [`InputValue`]. + pub fn get_input_value(&self, name: &str) -> Option<&Spanning>> { + self.args.as_ref().and_then(|args| args.get(name)) + } } /// Primary trait used to resolve GraphQL values. @@ -472,7 +478,8 @@ where m.item .iter() .filter_map(|(k, v)| { - v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) + let val = v.item.clone().into_const(exec_vars)?; + Some((k.item, Spanning::new(v.span, val))) }) .collect() }), diff --git a/juniper/src/types/subscriptions.rs b/juniper/src/types/subscriptions.rs index 4b575c82a..3e8211b8a 100644 --- a/juniper/src/types/subscriptions.rs +++ b/juniper/src/types/subscriptions.rs @@ -316,7 +316,8 @@ where m.item .iter() .filter_map(|(k, v)| { - v.item.clone().into_const(exec_vars).map(|v| (k.item, v)) + let val = v.item.clone().into_const(exec_vars)?; + Some((k.item, Spanning::new(v.span, val))) }) .collect() }), diff --git a/juniper/src/types/utilities.rs b/juniper/src/types/utilities.rs index f0e44b1f0..e8128eea0 100644 --- a/juniper/src/types/utilities.rs +++ b/juniper/src/types/utilities.rs @@ -72,11 +72,8 @@ where let mut remaining_required_fields = input_fields .iter() .filter_map(|f| { - if f.arg_type.is_non_null() && f.default_value.is_none() { - Some(&f.name) - } else { - None - } + (f.arg_type.is_non_null() && f.default_value.is_none()) + .then_some(&f.name) }) .collect::>();