Allow using Rust arrays as GraphQL lists (#918) (#966)

* Provide impls for arrays

* Remove redundant Default bound

* Recheck other places of mem::transmute usage

* Fix missing marker impls

* Extend GraphQL list validation with optional expected size

* Improve input object codegen

* Cover arrays with tests

* Add CHANGELOG entry

* Consider panic safety in FromInputValue implementation for array

* Tune up codegen failure tests
This commit is contained in:
Kai Ren 2021-07-24 03:51:47 +03:00 committed by GitHub
parent 8a90f867d4
commit 39d1e43420
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 629 additions and 70 deletions

View file

@ -0,0 +1,23 @@
use juniper::{graphql_interface, GraphQLObject};
#[derive(GraphQLObject)]
#[graphql(impl = CharacterValue)]
pub struct ObjA {
test: String,
}
#[graphql_interface]
impl Character for ObjA {}
#[graphql_interface(for = ObjA)]
trait Character {
fn wrong(
&self,
#[graphql(default = [true, false, false])]
input: [bool; 2],
) -> bool {
input[0]
}
}
fn main() {}

View file

@ -0,0 +1,12 @@
error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied
--> $DIR/argument_wrong_default_array.rs:12:1
|
12 | #[graphql_interface(for = ObjA)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]`
|
= help: the following implementations were found:
<&'a [ascii::ascii_char::AsciiChar] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a [u8] as From<&'a ascii::ascii_str::AsciiStr>>
<&'a mut [ascii::ascii_char::AsciiChar] as From<&'a mut ascii::ascii_str::AsciiStr>>
= note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]`
= note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,11 @@
struct Object;
#[juniper::graphql_object]
impl Object {
#[graphql(arguments(input(default = [true, false, false])))]
fn wrong(input: [bool; 2]) -> bool {
input[0]
}
}
fn main() {}

View file

@ -0,0 +1,7 @@
error[E0308]: mismatched types
--> $DIR/impl_argument_wrong_default_array.rs:3:1
|
3 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 2 elements, found one with 3 elements
|
= note: this error originates in the attribute macro `juniper::graphql_object` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -0,0 +1,257 @@
use juniper::{
graphql_object, graphql_value, EmptyMutation, EmptySubscription, GraphQLInputObject, RootNode,
Variables,
};
mod as_output_field {
use super::*;
struct Query;
#[graphql_object]
impl Query {
fn roll() -> [bool; 3] {
[true, false, true]
}
}
#[tokio::test]
async fn works() {
let query = r#"
query Query {
roll
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"roll": [true, false, true]}));
}
}
mod as_input_field {
use super::*;
#[derive(GraphQLInputObject)]
struct Input {
two: [bool; 2],
}
#[derive(GraphQLInputObject)]
struct InputSingle {
one: [bool; 1],
}
struct Query;
#[graphql_object]
impl Query {
fn first(input: InputSingle) -> bool {
input.one[0]
}
fn second(input: Input) -> bool {
input.two[1]
}
}
#[tokio::test]
async fn works() {
let query = r#"
query Query {
second(input: { two: [true, false] })
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"second": false}));
}
#[tokio::test]
async fn fails_on_incorrect_count() {
let query = r#"
query Query {
second(input: { two: [true, true, false] })
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &Variables::new(), &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
}
#[tokio::test]
async fn cannot_coerce_from_raw_value_if_multiple() {
let query = r#"
query Query {
second(input: { two: true })
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &Variables::new(), &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "Input!""#));
}
#[tokio::test]
async fn can_coerce_from_raw_value_if_single() {
let query = r#"
query Query {
first(input: { one: true })
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"first": true}));
}
}
mod as_input_argument {
use super::*;
struct Query;
#[graphql_object]
impl Query {
fn second(input: [bool; 2]) -> bool {
input[1]
}
fn first(input: [bool; 1]) -> bool {
input[0]
}
#[graphql(arguments(input(default = [true, false, false])))]
fn third(input: [bool; 3]) -> bool {
input[2]
}
}
#[tokio::test]
async fn works() {
let query = r#"
query Query {
second(input: [false, true])
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"second": true}));
}
#[tokio::test]
async fn fails_on_incorrect_count() {
let query = r#"
query Query {
second(input: [true, true, false])
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &Variables::new(), &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
}
#[tokio::test]
async fn cannot_coerce_from_raw_value_if_multiple() {
let query = r#"
query Query {
second(input: true)
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let res = juniper::execute(query, None, &schema, &Variables::new(), &()).await;
assert!(res.is_err());
assert!(res
.unwrap_err()
.to_string()
.contains(r#"Invalid value for argument "input", expected type "[Boolean!]!""#));
}
#[tokio::test]
async fn can_coerce_from_raw_value_if_single() {
let query = r#"
query Query {
first(input: true)
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"first": true}));
}
#[tokio::test]
async fn picks_default() {
let query = r#"
query Query {
third
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"third": false}));
}
#[tokio::test]
async fn picks_specified_over_default() {
let query = r#"
query Query {
third(input: [false, false, true])
}
"#;
let schema = RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new());
let (res, errors) = juniper::execute(query, None, &schema, &Variables::new(), &())
.await
.unwrap();
assert_eq!(errors.len(), 0);
assert_eq!(res, graphql_value!({"third": true}));
}
}

View file

@ -1,6 +1,8 @@
#[cfg(test)]
mod arc_fields;
#[cfg(test)]
mod array;
#[cfg(test)]
mod codegen;
#[cfg(test)]
mod custom_scalar;

View file

@ -2,6 +2,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))
- Expose `GraphQLRequest` fields ([#750](https://github.com/graphql-rust/juniper/issues/750))
- Support using Rust array as GraphQL list ([#966](https://github.com/graphql-rust/juniper/pull/966), [#918](https://github.com/graphql-rust/juniper/issues/918))
# [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7)

View file

@ -19,13 +19,13 @@ pub enum Type<'a> {
/// A nullable list type, e.g. `[String]`
///
/// The list itself is what's nullable, the containing type might be non-null.
List(Box<Type<'a>>),
List(Box<Type<'a>>, Option<usize>),
/// A non-null named type, e.g. `String!`
NonNullNamed(Cow<'a, str>),
/// A non-null list type, e.g. `[String]!`.
///
/// The list itself is what's non-null, the containing type might be null.
NonNullList(Box<Type<'a>>),
NonNullList(Box<Type<'a>>, Option<usize>),
}
/// A JSON-like value that can be passed into the query execution, either
@ -192,13 +192,13 @@ impl<'a> Type<'a> {
pub fn innermost_name(&self) -> &str {
match *self {
Type::Named(ref n) | Type::NonNullNamed(ref n) => n,
Type::List(ref l) | Type::NonNullList(ref l) => l.innermost_name(),
Type::List(ref l, _) | Type::NonNullList(ref l, _) => l.innermost_name(),
}
}
/// Determines if a type only can represent non-null values.
pub fn is_non_null(&self) -> bool {
matches!(*self, Type::NonNullNamed(_) | Type::NonNullList(_))
matches!(*self, Type::NonNullNamed(_) | Type::NonNullList(..))
}
}
@ -207,8 +207,8 @@ impl<'a> fmt::Display for Type<'a> {
match *self {
Type::Named(ref n) => write!(f, "{}", n),
Type::NonNullNamed(ref n) => write!(f, "{}!", n),
Type::List(ref t) => write!(f, "[{}]", t),
Type::NonNullList(ref t) => write!(f, "[{}]!", t),
Type::List(ref t, _) => write!(f, "[{}]", t),
Type::NonNullList(ref t, _) => write!(f, "[{}]!", t),
}
}
}

View file

@ -1235,9 +1235,10 @@ where
pub fn build_list_type<T: GraphQLType<S> + ?Sized>(
&mut self,
info: &T::TypeInfo,
expected_size: Option<usize>,
) -> ListMeta<'r> {
let of_type = self.get_type::<T>(info);
ListMeta::new(of_type)
ListMeta::new(of_type, expected_size)
}
/// Create a nullable meta type

View file

@ -520,7 +520,11 @@ pub fn parse_type<'a>(parser: &mut Parser<'a>) -> ParseResult<'a, Type<'a>> {
{
let inner_type = parse_type(parser)?;
let Spanning { end: end_pos, .. } = parser.expect(&Token::BracketClose)?;
Spanning::start_end(&start_pos, &end_pos, Type::List(Box::new(inner_type.item)))
Spanning::start_end(
&start_pos,
&end_pos,
Type::List(Box::new(inner_type.item), None),
)
} else {
parser.expect_name()?.map(|s| Type::Named(Cow::Borrowed(s)))
};
@ -542,7 +546,7 @@ fn wrap_non_null<'a>(
let wrapped = match inner.item {
Type::Named(name) => Type::NonNullNamed(name),
Type::List(l) => Type::NonNullList(l),
Type::List(l, expected_size) => Type::NonNullList(l, expected_size),
t => t,
};

View file

@ -55,6 +55,9 @@ pub struct ScalarMeta<'a, S> {
pub struct ListMeta<'a> {
#[doc(hidden)]
pub of_type: Type<'a>,
#[doc(hidden)]
pub expected_size: Option<usize>,
}
/// Nullable type metadata
@ -311,12 +314,15 @@ impl<'a, S> MetaType<'a, S> {
| MetaType::InputObject(InputObjectMeta { ref name, .. }) => {
Type::NonNullNamed(name.clone())
}
MetaType::List(ListMeta { ref of_type }) => {
Type::NonNullList(Box::new(of_type.clone()))
}
MetaType::List(ListMeta {
ref of_type,
expected_size,
}) => Type::NonNullList(Box::new(of_type.clone()), expected_size),
MetaType::Nullable(NullableMeta { ref of_type }) => match *of_type {
Type::NonNullNamed(ref inner) => Type::Named(inner.clone()),
Type::NonNullList(ref inner) => Type::List(inner.clone()),
Type::NonNullList(ref inner, expected_size) => {
Type::List(inner.clone(), expected_size)
}
ref t => t.clone(),
},
MetaType::Placeholder(PlaceholderMeta { ref of_type }) => of_type.clone(),
@ -446,8 +452,11 @@ where
impl<'a> ListMeta<'a> {
/// Build a new list type by wrapping the specified type
pub fn new(of_type: Type<'a>) -> ListMeta<'a> {
ListMeta { of_type }
pub fn new(of_type: Type<'a>, expected_size: Option<usize>) -> Self {
Self {
of_type,
expected_size,
}
}
/// Wrap the list in a generic meta type

View file

@ -62,7 +62,7 @@ impl<'a, S> Context for SchemaType<'a, S> {}
pub enum TypeType<'a, S: 'a> {
Concrete(&'a MetaType<'a, S>),
NonNull(Box<TypeType<'a, S>>),
List(Box<TypeType<'a, S>>),
List(Box<TypeType<'a, S>>, Option<usize>),
}
#[derive(Debug)]
@ -271,7 +271,7 @@ impl<'a, S> SchemaType<'a, S> {
Type::NonNullNamed(ref name) | Type::Named(ref name) => {
self.concrete_type_by_name(name)
}
Type::List(ref inner) | Type::NonNullList(ref inner) => self.lookup_type(inner),
Type::List(ref inner, _) | Type::NonNullList(ref inner, _) => self.lookup_type(inner),
}
}
@ -339,11 +339,13 @@ impl<'a, S> SchemaType<'a, S> {
Type::NonNullNamed(ref n) => TypeType::NonNull(Box::new(
self.type_by_name(n).expect("Type not found in schema"),
)),
Type::NonNullList(ref inner) => {
TypeType::NonNull(Box::new(TypeType::List(Box::new(self.make_type(inner)))))
}
Type::NonNullList(ref inner, expected_size) => TypeType::NonNull(Box::new(
TypeType::List(Box::new(self.make_type(inner)), expected_size),
)),
Type::Named(ref n) => self.type_by_name(n).expect("Type not found in schema"),
Type::List(ref inner) => TypeType::List(Box::new(self.make_type(inner))),
Type::List(ref inner, expected_size) => {
TypeType::List(Box::new(self.make_type(inner)), expected_size)
}
}
}
@ -423,9 +425,9 @@ impl<'a, S> SchemaType<'a, S> {
| (&Named(ref super_name), &NonNullNamed(ref sub_name)) => {
self.is_named_subtype(sub_name, super_name)
}
(&NonNullList(ref super_inner), &NonNullList(ref sub_inner))
| (&List(ref super_inner), &List(ref sub_inner))
| (&List(ref super_inner), &NonNullList(ref sub_inner)) => {
(&NonNullList(ref super_inner, _), &NonNullList(ref sub_inner, _))
| (&List(ref super_inner, _), &List(ref sub_inner, _))
| (&List(ref super_inner, _), &NonNullList(ref sub_inner, _)) => {
self.is_subtype(sub_inner, super_inner)
}
_ => false,
@ -460,14 +462,14 @@ impl<'a, S> TypeType<'a, S> {
pub fn innermost_concrete(&self) -> &'a MetaType<S> {
match *self {
TypeType::Concrete(t) => t,
TypeType::NonNull(ref n) | TypeType::List(ref n) => n.innermost_concrete(),
TypeType::NonNull(ref n) | TypeType::List(ref n, _) => n.innermost_concrete(),
}
}
#[inline]
pub fn list_contents(&self) -> Option<&TypeType<'a, S>> {
match *self {
TypeType::List(ref n) => Some(n),
TypeType::List(ref n, _) => Some(n),
TypeType::NonNull(ref n) => n.list_contents(),
_ => None,
}
@ -550,7 +552,7 @@ impl<'a, S> fmt::Display for TypeType<'a, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TypeType::Concrete(t) => f.write_str(t.name().unwrap()),
TypeType::List(ref i) => write!(f, "[{}]", i),
TypeType::List(ref i, _) => write!(f, "[{}]", i),
TypeType::NonNull(ref i) => write!(f, "{}!", i),
}
}

View file

@ -201,7 +201,7 @@ where
fn kind(&self) -> TypeKind {
match *self {
TypeType::Concrete(t) => t.type_kind(),
TypeType::List(_) => TypeKind::List,
TypeType::List(..) => TypeKind::List,
TypeType::NonNull(_) => TypeKind::NonNull,
}
}
@ -224,7 +224,7 @@ where
fn of_type(&self) -> Option<&Box<TypeType<S>>> {
match *self {
TypeType::Concrete(_) => None,
TypeType::List(ref l) | TypeType::NonNull(ref l) => Some(l),
TypeType::List(ref l, _) | TypeType::NonNull(ref l) => Some(l),
}
}

View file

@ -140,13 +140,13 @@ impl GraphQLParserTranslator {
{
match input {
Type::Named(x) => ExternalType::NamedType(From::from(x.as_ref())),
Type::List(x) => {
Type::List(x, _) => {
ExternalType::ListType(GraphQLParserTranslator::translate_type(x).into())
}
Type::NonNullNamed(x) => {
ExternalType::NonNullType(Box::new(ExternalType::NamedType(From::from(x.as_ref()))))
}
Type::NonNullList(x) => ExternalType::NonNullType(Box::new(ExternalType::ListType(
Type::NonNullList(x, _) => ExternalType::NonNullType(Box::new(ExternalType::ListType(
Box::new(GraphQLParserTranslator::translate_type(x)),
))),
}

View file

@ -1,3 +1,8 @@
use std::{
mem::{self, MaybeUninit},
ptr,
};
use crate::{
ast::{FromInputValue, InputValue, Selection, ToInputValue},
executor::{ExecutionResult, Executor, Registry},
@ -80,7 +85,7 @@ where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Option<T>> {
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
match v {
&InputValue::Null => Some(None),
v => v.convert().map(Some),
@ -114,7 +119,7 @@ where
where
S: 'r,
{
registry.build_list_type::<T>(info).into_meta()
registry.build_list_type::<T>(info, None).into_meta()
}
}
@ -163,17 +168,11 @@ where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Vec<T>>
where {
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
match *v {
InputValue::List(ref ls) => {
let v: Vec<_> = ls.iter().filter_map(|i| i.item.convert()).collect();
if v.len() == ls.len() {
Some(v)
} else {
None
}
(v.len() == ls.len()).then(|| v)
}
ref other => other.convert().map(|e| vec![e]),
}
@ -203,7 +202,7 @@ where
where
S: 'r,
{
registry.build_list_type::<T>(info).into_meta()
registry.build_list_type::<T>(info, None).into_meta()
}
}
@ -257,6 +256,179 @@ where
}
}
impl<S, T, const N: usize> GraphQLType<S> for [T; N]
where
S: ScalarValue,
T: GraphQLType<S>,
{
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
None
}
fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S>
where
S: 'r,
{
registry.build_list_type::<T>(info, Some(N)).into_meta()
}
}
impl<S, T, const N: usize> GraphQLValue<S> for [T; N]
where
S: ScalarValue,
T: GraphQLValue<S>,
{
type Context = T::Context;
type TypeInfo = T::TypeInfo;
fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> {
None
}
fn resolve(
&self,
info: &Self::TypeInfo,
_: Option<&[Selection<S>]>,
executor: &Executor<Self::Context, S>,
) -> ExecutionResult<S> {
resolve_into_list(executor, info, self.iter())
}
}
impl<S, T, const N: usize> GraphQLValueAsync<S> for [T; N]
where
T: GraphQLValueAsync<S>,
T::TypeInfo: Sync,
T::Context: Sync,
S: ScalarValue + Send + Sync,
{
fn resolve_async<'a>(
&'a self,
info: &'a Self::TypeInfo,
_: Option<&'a [Selection<S>]>,
executor: &'a Executor<Self::Context, S>,
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
let f = resolve_into_list_async(executor, info, self.iter());
Box::pin(f)
}
}
impl<T, S, const N: usize> FromInputValue<S> for [T; N]
where
T: FromInputValue<S>,
S: ScalarValue,
{
fn from_input_value(v: &InputValue<S>) -> Option<Self> {
struct PartiallyInitializedArray<T, const N: usize> {
arr: [MaybeUninit<T>; N],
init_len: usize,
no_drop: bool,
}
impl<T, const N: usize> Drop for PartiallyInitializedArray<T, N> {
fn drop(&mut self) {
if self.no_drop {
return;
}
// Dropping a `MaybeUninit` does nothing, thus we need to drop
// the initialized elements manually, otherwise we may introduce
// a memory/resource leak if `T: Drop`.
for elem in &mut self.arr[0..self.init_len] {
// SAFETY: This is safe, because `self.init_len` represents
// exactly the number of initialized elements.
unsafe {
ptr::drop_in_place(elem.as_mut_ptr());
}
}
}
}
match *v {
InputValue::List(ref ls) => {
// SAFETY: The reason we're using a wrapper struct implementing
// `Drop` here is to be panic safe:
// `T: FromInputValue<S>` implementation is not
// controlled by us, so calling `i.item.convert()` below
// may cause a panic when our array is initialized only
// partially. In such situation we need to drop already
// initialized values to avoid possible memory/resource
// leaks if `T: Drop`.
let mut out = PartiallyInitializedArray::<T, N> {
// SAFETY: The `.assume_init()` here is safe, because the
// type we are claiming to have initialized here is
// a bunch of `MaybeUninit`s, which do not require
// any initialization.
arr: unsafe { MaybeUninit::uninit().assume_init() },
init_len: 0,
no_drop: false,
};
let mut items = ls.iter().filter_map(|i| i.item.convert());
for elem in &mut out.arr[..] {
if let Some(i) = items.next() {
*elem = MaybeUninit::new(i);
out.init_len += 1;
} else {
// There is not enough `items` to fill the array.
return None;
}
}
if items.next().is_some() {
// There is too much `items` to fit into the array.
return None;
}
// Do not drop collected `items`, because we're going to return
// them.
out.no_drop = true;
// 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 have
// exactly `N` initialized `items`.
// Also, despite `mem::transmute_copy` copies the value,
// we won't have a double-free when `T: Drop` here,
// because original array elements are `MaybeUninit`, so
// do nothing on `Drop`.
Some(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
}
ref other => {
other.convert().and_then(|e: T| {
// 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
if N == 1 {
// SAFETY: `mem::transmute_copy` is safe here, because
// we check `N` to be `1`.
// Also, despite `mem::transmute_copy` copies
// the value, we won't have a double-free when
// `T: Drop` here, because original `e: T` value
// is wrapped into `mem::ManuallyDrop`, so does
// nothing on `Drop`.
Some(unsafe {
mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)])
})
} else {
None
}
})
}
}
}
}
impl<T, S, const N: usize> ToInputValue<S> for [T; N]
where
T: ToInputValue<S>,
S: ScalarValue,
{
fn to_input_value(&self) -> InputValue<S> {
InputValue::list(self.iter().map(T::to_input_value).collect())
}
}
fn resolve_into_list<'t, S, T, I>(
executor: &Executor<T::Context, S>,
info: &T::TypeInfo,

View file

@ -253,6 +253,17 @@ where
}
}
impl<S, T, const N: usize> IsOutputType<S> for [T; N]
where
T: IsOutputType<S>,
S: ScalarValue,
{
#[inline]
fn mark() {
T::mark()
}
}
impl<'a, S> IsOutputType<S> for str where S: ScalarValue {}
/// Marker trait for types which can be used as input types.
@ -335,4 +346,15 @@ where
}
}
impl<S, T, const N: usize> IsInputType<S> for [T; N]
where
T: IsInputType<S>,
S: ScalarValue,
{
#[inline]
fn mark() {
T::mark()
}
}
impl<'a, S> IsInputType<S> for str where S: ScalarValue {}

View file

@ -24,11 +24,25 @@ where
is_valid_literal_value(schema, inner, arg_value)
}
}
TypeType::List(ref inner) => match *arg_value {
InputValue::List(ref items) => items
.iter()
.all(|i| is_valid_literal_value(schema, inner, &i.item)),
ref v => is_valid_literal_value(schema, inner, v),
TypeType::List(ref inner, expected_size) => match *arg_value {
InputValue::List(ref items) => {
if let Some(expected) = expected_size {
if items.len() != expected {
return false;
}
}
items
.iter()
.all(|i| is_valid_literal_value(schema, inner, &i.item))
}
ref v => {
if let Some(expected) = expected_size {
if expected != 1 {
return false;
}
}
is_valid_literal_value(schema, inner, v)
}
},
TypeType::Concrete(t) => {
// Even though InputValue::String can be parsed into an enum, they

View file

@ -108,13 +108,27 @@ where
}
}
TypeType::List(ref inner) => {
TypeType::List(ref inner, expected_size) => {
if value.is_null() {
return errors;
}
match value.to_list_value() {
Some(l) => {
if let Some(expected) = expected_size {
if l.len() != expected {
errors.push(unification_error(
var_name,
var_pos,
&path,
&format!(
"Expected list of {} elements, found {} elements",
expected,
l.len()
),
));
}
}
for (i, v) in l.iter().enumerate() {
errors.append(&mut unify_value(
var_name,

View file

@ -529,8 +529,14 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> {
fn is_type_conflict(&self, ctx: &ValidatorContext<'a, S>, t1: &Type, t2: &Type) -> bool {
match (t1, t2) {
(&Type::List(ref inner1), &Type::List(ref inner2))
| (&Type::NonNullList(ref inner1), &Type::NonNullList(ref inner2)) => {
(&Type::List(ref inner1, expected_size1), &Type::List(ref inner2, expected_size2))
| (
&Type::NonNullList(ref inner1, expected_size1),
&Type::NonNullList(ref inner2, expected_size2),
) => {
if expected_size1 != expected_size2 {
return false;
}
self.is_type_conflict(ctx, inner1, inner2)
}
(&Type::NonNullNamed(ref n1), &Type::NonNullNamed(ref n2))

View file

@ -54,7 +54,9 @@ impl<'a, S: Debug> VariableInAllowedPosition<'a, S> {
.find(|&&&(ref n, _)| n.item == var_name.item)
{
let expected_type = match (&var_def.default_value, &var_def.var_type.item) {
(&Some(_), &Type::List(ref inner)) => Type::NonNullList(inner.clone()),
(&Some(_), &Type::List(ref inner, expected_size)) => {
Type::NonNullList(inner.clone(), expected_size)
}
(&Some(_), &Type::Named(ref inner)) => {
Type::NonNullNamed(Cow::Borrowed(inner))
}

View file

@ -1,3 +1,5 @@
use std::mem;
use crate::{
ast::{FromInputValue, InputValue},
executor::Registry,
@ -862,12 +864,10 @@ where
let doc =
parse_document_source(q, &root.schema).expect(&format!("Parse error on input {:#?}", q));
let mut ctx = ValidatorContext::new(unsafe { ::std::mem::transmute(&root.schema) }, &doc);
let mut ctx = ValidatorContext::new(unsafe { mem::transmute(&root.schema) }, &doc);
let mut mv = MultiVisitorNil.with(factory());
visit(&mut mv, &mut ctx, unsafe {
::std::mem::transmute(doc.as_slice())
});
visit(&mut mv, &mut ctx, unsafe { mem::transmute(doc.as_slice()) });
ctx.into_errors()
}

View file

@ -336,7 +336,7 @@ fn visit_input_value<'a, S, V>(
}
InputValue::List(ref ls) => {
let inner_type = ctx.current_input_type_literal().and_then(|t| match *t {
Type::List(ref inner) | Type::NonNullList(ref inner) => {
Type::List(ref inner, _) | Type::NonNullList(ref inner, _) => {
Some(inner.as_ref().clone())
}
_ => None,

View file

@ -164,8 +164,12 @@ impl<S: ScalarValue> Value<S> {
/// Maps the [`ScalarValue`] type of this [`Value`] into the specified one.
pub fn map_scalar_value<Into: ScalarValue>(self) -> Value<Into> {
if TypeId::of::<Into>() == TypeId::of::<S>() {
// This is totally safe, because we're transmuting the value into itself,
// so no invariants may change and we're just satisfying the type checker.
// SAFETY: This is safe, because we're transmuting the value into
// itself, so no invariants may change and we're just
// satisfying the type checker.
// As `mem::transmute_copy` creates a copy of data, we need
// `mem::ManuallyDrop` here to omit double-free when
// `S: Drop`.
let val = mem::ManuallyDrop::new(self);
unsafe { mem::transmute_copy(&*val) }
} else {

View file

@ -1731,7 +1731,7 @@ impl GraphQLTypeDefiniton {
// TODO: investigate the unwraps here, they seem dangerous!
match obj.get(#field_name) {
#from_input_default
Some(ref v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
Some(ref v) => ::juniper::FromInputValue::from_input_value(v)?,
None => ::juniper::FromInputValue::<#scalar>::from_implicit_null(),
}
},
@ -1849,15 +1849,10 @@ impl GraphQLTypeDefiniton {
{
fn from_input_value(value: &::juniper::InputValue<#scalar>) -> Option<Self>
{
if let Some(obj) = value.to_object_value() {
let item = #ty {
#( #from_inputs )*
};
Some(item)
}
else {
None
}
let obj = value.to_object_value()?;
Some(#ty {
#( #from_inputs )*
})
}
}

View file

@ -1,6 +1,7 @@
use std::{any::Any, fmt, marker::PhantomPinned, mem};
use juniper::{ExecutionError, GraphQLError, ScalarValue, Value};
use serde::{Serialize, Serializer};
use std::{any::Any, fmt, marker::PhantomPinned};
/// The payload for errors that are not associated with a GraphQL operation.
#[derive(Debug, Serialize, PartialEq)]
@ -45,7 +46,7 @@ impl ErrorPayload {
) -> Self {
Self {
_execution_params: Some(execution_params),
error: std::mem::transmute(error),
error: mem::transmute(error),
_marker: PhantomPinned,
}
}