Fix list input coercion rules (#1004)
Additionally: - fix WASM builds after 2.1 version of `bson`
This commit is contained in:
parent
46be97ada4
commit
265d4c5bb2
4 changed files with 289 additions and 32 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")?,
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue