* 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:
parent
8a90f867d4
commit
39d1e43420
25 changed files with 629 additions and 70 deletions
|
@ -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() {}
|
|
@ -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)
|
|
@ -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() {}
|
|
@ -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)
|
257
integration_tests/juniper_tests/src/array.rs
Normal file
257
integration_tests/juniper_tests/src/array.rs
Normal 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}));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
#[cfg(test)]
|
||||
mod arc_fields;
|
||||
#[cfg(test)]
|
||||
mod array;
|
||||
#[cfg(test)]
|
||||
mod codegen;
|
||||
#[cfg(test)]
|
||||
mod custom_scalar;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
))),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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
|
||||
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 => is_valid_literal_value(schema, inner, v),
|
||||
.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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
let obj = value.to_object_value()?;
|
||||
Some(#ty {
|
||||
#( #from_inputs )*
|
||||
};
|
||||
Some(item)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue