Fix list input coercion rules (#1004)

Additionally:
- fix WASM builds after 2.1 version of `bson`
This commit is contained in:
ilslv 2021-12-16 13:36:53 +03:00 committed by GitHub
parent 46be97ada4
commit 265d4c5bb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 289 additions and 32 deletions

View file

@ -24,6 +24,7 @@
- Allow spreading interface fragments on unions and other interfaces. ([#965](https://github.com/graphql-rust/juniper/pull/965), [#798](https://github.com/graphql-rust/juniper/issues/798))
- Support expressions in `graphql_value!` macro. ([#996](https://github.com/graphql-rust/juniper/pull/996), [#503](https://github.com/graphql-rust/juniper/issues/503))
- List coercion rules: `null` cannot be coerced to an `[Int!]!` or `[Int]!`. ([#1004](https://github.com/graphql-rust/juniper/pull/1004))
# [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7)

View file

@ -49,10 +49,13 @@ serde_json = { version = "1.0.2", default-features = false, optional = true }
smartstring = "0.2.6"
static_assertions = "1.1"
url = { version = "2.0", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
uuid = { version = "0.8", default-features = false, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
uuid = { version = "0.8", default-features = false, features = ["wasm-bindgen"], optional = true }
[dev-dependencies]
bencher = "0.1.2"

View file

@ -157,17 +157,22 @@ where
}
}
impl<S, T: FromInputValue<S>> FromInputValue<S> for Vec<T> {
type Error = T::Error;
impl<S: ScalarValue, T: FromInputValue<S>> FromInputValue<S> for Vec<T> {
type Error = FromInputValueVecError<T, S>;
fn from_input_value(v: &InputValue<S>) -> Result<Self, Self::Error> {
match v {
InputValue::List(l) => l.iter().map(|i| i.item.convert()).collect(),
InputValue::List(l) => l
.iter()
.map(|i| i.item.convert().map_err(FromInputValueVecError::Item))
.collect(),
// See "Input Coercion" on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
// In reality is intercepted by `Option`.
InputValue::Null => Ok(Vec::new()),
other => other.convert().map(|e| vec![e]),
// https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
InputValue::Null => Err(FromInputValueVecError::Null),
other => other
.convert()
.map(|e| vec![e])
.map_err(FromInputValueVecError::Item),
}
}
}
@ -182,6 +187,39 @@ where
}
}
/// Possible errors of converting [`InputValue`] into [`Vec`].
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FromInputValueVecError<T, S>
where
T: FromInputValue<S>,
S: ScalarValue,
{
/// [`InputValue`] cannot be [`Null`].
///
/// See ["Combining List and Non-Null" section of spec][1].
///
/// [`Null`]: [`InputValue::Null`]
/// [1]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
Null,
/// Error of converting [`InputValue::List`]'s item.
Item(T::Error),
}
impl<T, S> IntoFieldError<S> for FromInputValueVecError<T, S>
where
T: FromInputValue<S>,
T::Error: IntoFieldError<S>,
S: ScalarValue,
{
fn into_field_error(self) -> FieldError<S> {
match self {
Self::Null => "Failed to convert into `Vec`: Value cannot be `null`".into(),
Self::Item(s) => s.into_field_error(),
}
}
}
impl<S, T> GraphQLType<S> for [T]
where
S: ScalarValue,
@ -379,7 +417,7 @@ where
if let Some(i) = items
.next()
.transpose()
.map_err(FromInputValueArrayError::Scalar)?
.map_err(FromInputValueArrayError::Item)?
{
*elem = MaybeUninit::new(i);
out.init_len += 1;
@ -402,21 +440,12 @@ where
Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
}
// See "Input Coercion" on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
// In reality is intercepted by `Option`.
InputValue::Null if N == 0 => {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
// SAFETY: `mem::transmute_copy` is safe here, because we check
// `N` to be `0`. It's no-op, actually.
Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) })
}
// https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
InputValue::Null => Err(FromInputValueArrayError::Null),
ref other => {
other
.convert()
.map_err(FromInputValueArrayError::Scalar)
.map_err(FromInputValueArrayError::Item)
.and_then(|e: T| {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
@ -457,22 +486,31 @@ where
}
/// Error converting [`InputValue`] into exact-size [`array`](prim@array).
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FromInputValueArrayError<T, S>
where
T: FromInputValue<S>,
S: ScalarValue,
{
/// Wrong count of elements.
/// [`InputValue`] cannot be [`Null`].
///
/// See ["Combining List and Non-Null" section of spec][1].
///
/// [`Null`]: [`InputValue::Null`]
/// [1]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
Null,
/// Wrong count of items.
WrongCount {
/// Actual count of elements.
/// Actual count of items.
actual: usize,
/// Expected count of elements.
/// Expected count of items.
expected: usize,
},
/// Underlying [`ScalarValue`] conversion error.
Scalar(T::Error),
/// Error of converting [`InputValue::List`]'s item.
Item(T::Error),
}
impl<T, S> IntoFieldError<S> for FromInputValueArrayError<T, S>
@ -484,12 +522,13 @@ where
fn into_field_error(self) -> FieldError<S> {
const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
match self {
Self::Null => format!("{}: Value cannot be `null`", ERROR_PREFIX).into(),
Self::WrongCount { actual, expected } => format!(
"{}: wrong elements count: {} instead of {}",
ERROR_PREFIX, actual, expected
)
.into(),
Self::Scalar(s) => s.into_field_error(),
Self::Item(s) => s.into_field_error(),
}
}
}
@ -562,14 +601,228 @@ where
mod coercion {
use crate::{graphql_input_value, FromInputValue as _, InputValue};
use super::{FromInputValueArrayError, FromInputValueVecError};
type V = InputValue;
#[test]
fn option() {
let v: V = graphql_input_value!(null);
assert_eq!(<Option<i32>>::from_input_value(&v), Ok(None));
let v: V = graphql_input_value!(1);
assert_eq!(<Option<i32>>::from_input_value(&v), Ok(Some(1)));
}
// See "Input Coercion" examples on List types:
// https://spec.graphql.org/June2018/#sec-Type-System.List
// https://spec.graphql.org/October2021/#sec-List.Input-Coercion
#[test]
fn vec() {
let v: V = graphql_input_value!(null);
assert_eq!(
<Vec<i32>>::from_input_value(&v),
Err(FromInputValueVecError::Null),
);
assert_eq!(
<Vec<Option<i32>>>::from_input_value(&v),
Err(FromInputValueVecError::Null),
);
assert_eq!(<Option<Vec<i32>>>::from_input_value(&v), Ok(None));
assert_eq!(<Option<Vec<Option<i32>>>>::from_input_value(&v), Ok(None));
assert_eq!(
<Vec<Vec<i32>>>::from_input_value(&v),
Err(FromInputValueVecError::Null),
);
assert_eq!(
<Option<Vec<Option<Vec<Option<i32>>>>>>::from_input_value(&v),
Ok(None),
);
let v: V = graphql_input_value!(1);
assert_eq!(<Vec<i32>>::from_input_value(&v), Ok(vec![1]));
assert_eq!(<Vec<Option<i32>>>::from_input_value(&v), Ok(vec![Some(1)]));
assert_eq!(<Option<Vec<i32>>>::from_input_value(&v), Ok(Some(vec![1])));
assert_eq!(
<Option<Vec<Option<i32>>>>::from_input_value(&v),
Ok(Some(vec![Some(1)])),
);
assert_eq!(<Vec<Vec<i32>>>::from_input_value(&v), Ok(vec![vec![1]]));
assert_eq!(
<Option<Vec<Option<Vec<Option<i32>>>>>>::from_input_value(&v),
Ok(Some(vec![Some(vec![Some(1)])])),
);
let v: V = graphql_input_value!([1, 2, 3]);
assert_eq!(<Vec<i32>>::from_input_value(&v), Ok(vec![1, 2, 3]),);
// TODO: all examples
assert_eq!(<Vec<i32>>::from_input_value(&v), Ok(vec![1, 2, 3]));
assert_eq!(
<Option<Vec<i32>>>::from_input_value(&v),
Ok(Some(vec![1, 2, 3])),
);
assert_eq!(
<Vec<Option<i32>>>::from_input_value(&v),
Ok(vec![Some(1), Some(2), Some(3)]),
);
assert_eq!(
<Option<Vec<Option<i32>>>>::from_input_value(&v),
Ok(Some(vec![Some(1), Some(2), Some(3)])),
);
assert_eq!(
<Vec<Vec<i32>>>::from_input_value(&v),
Ok(vec![vec![1], vec![2], vec![3]]),
);
// Looks like the spec ambiguity.
// See: https://github.com/graphql/graphql-spec/pull/515
assert_eq!(
<Option<Vec<Option<Vec<Option<i32>>>>>>::from_input_value(&v),
Ok(Some(vec![
Some(vec![Some(1)]),
Some(vec![Some(2)]),
Some(vec![Some(3)]),
])),
);
let v: V = graphql_input_value!([1, 2, null]);
assert_eq!(
<Vec<i32>>::from_input_value(&v),
Err(FromInputValueVecError::Item(
"Expected `Int`, found: null".to_owned(),
)),
);
assert_eq!(
<Option<Vec<i32>>>::from_input_value(&v),
Err(FromInputValueVecError::Item(
"Expected `Int`, found: null".to_owned(),
)),
);
assert_eq!(
<Vec<Option<i32>>>::from_input_value(&v),
Ok(vec![Some(1), Some(2), None]),
);
assert_eq!(
<Option<Vec<Option<i32>>>>::from_input_value(&v),
Ok(Some(vec![Some(1), Some(2), None])),
);
assert_eq!(
<Vec<Vec<i32>>>::from_input_value(&v),
Err(FromInputValueVecError::Item(FromInputValueVecError::Null)),
);
// Looks like the spec ambiguity.
// See: https://github.com/graphql/graphql-spec/pull/515
assert_eq!(
<Option<Vec<Option<Vec<Option<i32>>>>>>::from_input_value(&v),
Ok(Some(vec![Some(vec![Some(1)]), Some(vec![Some(2)]), None])),
);
}
// See "Input Coercion" examples on List types:
// https://spec.graphql.org/October2021#sec-List.Input-Coercion
#[test]
fn array() {
let v: V = graphql_input_value!(null);
assert_eq!(
<[i32; 0]>::from_input_value(&v),
Err(FromInputValueArrayError::Null),
);
assert_eq!(
<[i32; 1]>::from_input_value(&v),
Err(FromInputValueArrayError::Null),
);
assert_eq!(
<[Option<i32>; 0]>::from_input_value(&v),
Err(FromInputValueArrayError::Null),
);
assert_eq!(
<[Option<i32>; 1]>::from_input_value(&v),
Err(FromInputValueArrayError::Null),
);
assert_eq!(<Option<[i32; 0]>>::from_input_value(&v), Ok(None));
assert_eq!(<Option<[i32; 1]>>::from_input_value(&v), Ok(None));
assert_eq!(<Option<[Option<i32>; 0]>>::from_input_value(&v), Ok(None));
assert_eq!(<Option<[Option<i32>; 1]>>::from_input_value(&v), Ok(None));
assert_eq!(
<[[i32; 1]; 1]>::from_input_value(&v),
Err(FromInputValueArrayError::Null),
);
assert_eq!(
<Option<[Option<[Option<i32>; 1]>; 1]>>::from_input_value(&v),
Ok(None),
);
let v: V = graphql_input_value!(1);
assert_eq!(<[i32; 1]>::from_input_value(&v), Ok([1]));
assert_eq!(
<[i32; 0]>::from_input_value(&v),
Err(FromInputValueArrayError::WrongCount {
expected: 0,
actual: 1,
}),
);
assert_eq!(<[Option<i32>; 1]>::from_input_value(&v), Ok([Some(1)]));
assert_eq!(<Option<[i32; 1]>>::from_input_value(&v), Ok(Some([1])));
assert_eq!(
<Option<[Option<i32>; 1]>>::from_input_value(&v),
Ok(Some([Some(1)])),
);
assert_eq!(<[[i32; 1]; 1]>::from_input_value(&v), Ok([[1]]));
assert_eq!(
<Option<[Option<[Option<i32>; 1]>; 1]>>::from_input_value(&v),
Ok(Some([Some([Some(1)])])),
);
let v: V = graphql_input_value!([1, 2, 3]);
assert_eq!(<[i32; 3]>::from_input_value(&v), Ok([1, 2, 3]));
assert_eq!(
<Option<[i32; 3]>>::from_input_value(&v),
Ok(Some([1, 2, 3])),
);
assert_eq!(
<[Option<i32>; 3]>::from_input_value(&v),
Ok([Some(1), Some(2), Some(3)]),
);
assert_eq!(
<Option<[Option<i32>; 3]>>::from_input_value(&v),
Ok(Some([Some(1), Some(2), Some(3)])),
);
assert_eq!(<[[i32; 1]; 3]>::from_input_value(&v), Ok([[1], [2], [3]]));
// Looks like the spec ambiguity.
// See: https://github.com/graphql/graphql-spec/pull/515
assert_eq!(
<Option<[Option<[Option<i32>; 1]>; 3]>>::from_input_value(&v),
Ok(Some([Some([Some(1)]), Some([Some(2)]), Some([Some(3)]),])),
);
let v: V = graphql_input_value!([1, 2, null]);
assert_eq!(
<[i32; 3]>::from_input_value(&v),
Err(FromInputValueArrayError::Item(
"Expected `Int`, found: null".to_owned(),
)),
);
assert_eq!(
<Option<[i32; 3]>>::from_input_value(&v),
Err(FromInputValueArrayError::Item(
"Expected `Int`, found: null".to_owned(),
)),
);
assert_eq!(
<[Option<i32>; 3]>::from_input_value(&v),
Ok([Some(1), Some(2), None]),
);
assert_eq!(
<Option<[Option<i32>; 3]>>::from_input_value(&v),
Ok(Some([Some(1), Some(2), None])),
);
assert_eq!(
<[[i32; 1]; 3]>::from_input_value(&v),
Err(FromInputValueArrayError::Item(
FromInputValueArrayError::Null
)),
);
// Looks like the spec ambiguity.
// See: https://github.com/graphql/graphql-spec/pull/515
assert_eq!(
<Option<[Option<[Option<i32>; 1]>; 3]>>::from_input_value(&v),
Ok(Some([Some([Some(1)]), Some([Some(2)]), None])),
);
}
}

View file

@ -14,7 +14,7 @@ use crate::{
},
validation::{visit, MultiVisitorNil, RuleError, ValidatorContext, Visitor},
value::ScalarValue,
GraphQLInputObject,
FieldError, GraphQLInputObject, IntoFieldError,
};
struct Being;
@ -616,7 +616,7 @@ impl<S> FromInputValue<S> for ComplexInput
where
S: ScalarValue,
{
type Error = String;
type Error = FieldError<S>;
fn from_input_value<'a>(v: &InputValue<S>) -> Result<ComplexInput, Self::Error> {
let obj = v.to_object_value().ok_or("Expected object")?;
@ -644,7 +644,7 @@ where
.ok_or("Expected booleanField")?,
string_list_field: obj
.get("stringListField")
.map(|v| v.convert())
.map(|v| v.convert().map_err(IntoFieldError::into_field_error))
.transpose()?
.ok_or("Expected stringListField")?,
})