Re-enable marks-based static checks in code generated by macros (#751)

- add associated type to IntoResolvable and IntoFieldResult traits allowing to name the GraphQLType being resolved
- relax Sized requirement on some IsInputType and IsOutputType impls
This commit is contained in:
Kai Ren 2020-09-02 22:48:54 +03:00 committed by GitHub
parent 2ab00f55d6
commit a684e1d91c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 136 additions and 44 deletions

View file

@ -1,3 +1,12 @@
error[E0277]: the trait bound `ObjectA: juniper::marker::IsInputType<__S>` is not satisfied
--> $DIR/derive_incompatible_object.rs:6:10
|
6 | #[derive(juniper::GraphQLInputObject)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::marker::IsInputType<__S>` is not implemented for `ObjectA`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `ObjectA: juniper::FromInputValue<__S>` is not satisfied error[E0277]: the trait bound `ObjectA: juniper::FromInputValue<__S>` is not satisfied
--> $DIR/derive_incompatible_object.rs:6:10 --> $DIR/derive_incompatible_object.rs:6:10
| |

View file

@ -1,3 +1,12 @@
error[E0277]: the trait bound `Obj: juniper::marker::IsInputType<juniper::DefaultScalarValue>` is not satisfied
--> $DIR/impl_argument_no_object.rs:8:1
|
8 | #[juniper::graphql_object]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `juniper::marker::IsInputType<juniper::DefaultScalarValue>` is not implemented for `Obj`
|
= note: required by `juniper::marker::IsInputType::mark`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: the trait bound `Obj: juniper::FromInputValue` is not satisfied error[E0277]: the trait bound `Obj: juniper::FromInputValue` is not satisfied
--> $DIR/impl_argument_no_object.rs:8:1 --> $DIR/impl_argument_no_object.rs:8:1
| |

View file

@ -1,8 +1,8 @@
use fnv::FnvHashMap; use fnv::FnvHashMap;
use juniper::{ use juniper::{
DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue, InputValue, marker, DefaultScalarValue, FromInputValue, GraphQLInputObject, GraphQLType, GraphQLValue,
ToInputValue, InputValue, ToInputValue,
}; };
#[derive(GraphQLInputObject, Debug, PartialEq)] #[derive(GraphQLInputObject, Debug, PartialEq)]
@ -50,6 +50,8 @@ struct OverrideDocComment {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
struct Fake; struct Fake;
impl<'a> marker::IsInputType<DefaultScalarValue> for &'a Fake {}
impl<'a> FromInputValue for &'a Fake { impl<'a> FromInputValue for &'a Fake {
fn from_input_value(_v: &InputValue) -> Option<&'a Fake> { fn from_input_value(_v: &InputValue) -> Option<&'a Fake> {
None None

View file

@ -3,7 +3,7 @@ use juniper::DefaultScalarValue;
use juniper::Object; use juniper::Object;
#[cfg(test)] #[cfg(test)]
use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables}; use juniper::{execute, EmptyMutation, EmptySubscription, FieldError, RootNode, Value, Variables};
pub struct MyObject; pub struct MyObject;
@ -84,3 +84,16 @@ where
f((type_info, fields)); f((type_info, fields));
} }
mod fallible {
use super::*;
struct Obj;
#[juniper::graphql_object]
impl Obj {
fn test(&self, arg: String) -> Result<String, FieldError> {
Ok(arg)
}
}
}

View file

@ -253,6 +253,8 @@ where
T: GraphQLValue<S>, T: GraphQLValue<S>,
S: ScalarValue, S: ScalarValue,
{ {
type Type;
#[doc(hidden)] #[doc(hidden)]
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S>; fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S>;
} }
@ -263,6 +265,8 @@ where
S: ScalarValue, S: ScalarValue,
T::Context: FromContext<C>, T::Context: FromContext<C>,
{ {
type Type = T;
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some((FromContext::from(ctx), self))) Ok(Some((FromContext::from(ctx), self)))
} }
@ -274,6 +278,8 @@ where
T: GraphQLValue<S>, T: GraphQLValue<S>,
T::Context: FromContext<C>, T::Context: FromContext<C>,
{ {
type Type = T;
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
self.map(|v: T| Some((<T::Context as FromContext<C>>::from(ctx), v))) self.map(|v: T| Some((<T::Context as FromContext<C>>::from(ctx), v)))
.map_err(IntoFieldError::into_field_error) .map_err(IntoFieldError::into_field_error)
@ -285,6 +291,8 @@ where
S: ScalarValue, S: ScalarValue,
T: GraphQLValue<S>, T: GraphQLValue<S>,
{ {
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some(self)) Ok(Some(self))
} }
@ -295,6 +303,8 @@ where
S: ScalarValue, S: ScalarValue,
T: GraphQLValue<S>, T: GraphQLValue<S>,
{ {
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> { fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> {
Ok(self.map(|(ctx, v)| (ctx, Some(v)))) Ok(self.map(|(ctx, v)| (ctx, Some(v))))
} }
@ -305,6 +315,8 @@ where
S: ScalarValue, S: ScalarValue,
T: GraphQLValue<S>, T: GraphQLValue<S>,
{ {
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> { fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
self.map(Some) self.map(Some)
} }
@ -316,6 +328,8 @@ where
S: ScalarValue, S: ScalarValue,
T: GraphQLValue<S>, T: GraphQLValue<S>,
{ {
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> { fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> {
self.map(|o| o.map(|(ctx, v)| (ctx, Some(v)))) self.map(|o| o.map(|(ctx, v)| (ctx, Some(v))))
} }

View file

@ -183,7 +183,7 @@ pub use crate::{
types::{ types::{
async_await::{GraphQLTypeAsync, GraphQLValueAsync}, async_await::{GraphQLTypeAsync, GraphQLValueAsync},
base::{Arguments, GraphQLType, GraphQLValue, TypeKind}, base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
marker::{self, GraphQLUnion}, marker::{self, GraphQLUnion, IsOutputType},
scalars::{EmptyMutation, EmptySubscription, ID}, scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{ subscriptions::{
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue, ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,

View file

@ -178,6 +178,10 @@ macro_rules! graphql_interface {
} }
); );
$crate::__juniper_impl_trait!(
impl<$($scalar)* $(, $lifetimes)* > IsOutputType for $name { }
);
$crate::__juniper_impl_trait!( $crate::__juniper_impl_trait!(
impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name { impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name {
type Context = $ctx; type Context = $ctx;

View file

@ -7,27 +7,36 @@ use futures::Stream;
use crate::{FieldError, GraphQLValue, ScalarValue}; use crate::{FieldError, GraphQLValue, ScalarValue};
/// Trait for converting `T` to `Ok(T)` if T is not Result. /// Trait for wrapping [`Stream`] into [`Ok`] if it's not [`Result`].
/// This is useful in subscription macros when user can provide type alias for ///
/// Stream or Result<Stream, _> and then a function on Stream should be called. /// Used in subscription macros when user can provide type alias for [`Stream`] or
/// `Result<Stream, _>` and then a function on [`Stream`] should be called.
pub trait IntoFieldResult<T, S> { pub trait IntoFieldResult<T, S> {
/// Turn current type into a generic result /// Type of items yielded by this [`Stream`].
type Item;
/// Turns current [`Stream`] type into a generic [`Result`].
fn into_result(self) -> Result<T, FieldError<S>>; fn into_result(self) -> Result<T, FieldError<S>>;
} }
impl<T, E, S> IntoFieldResult<T, S> for Result<T, E> impl<T, E, S> IntoFieldResult<T, S> for Result<T, E>
where where
T: IntoFieldResult<T, S>,
E: Into<FieldError<S>>, E: Into<FieldError<S>>,
{ {
type Item = T::Item;
fn into_result(self) -> Result<T, FieldError<S>> { fn into_result(self) -> Result<T, FieldError<S>> {
self.map_err(|e| e.into()) self.map_err(|e| e.into())
} }
} }
impl<T, I, S> IntoFieldResult<T, S> for T impl<T, S> IntoFieldResult<T, S> for T
where where
T: Stream<Item = I>, T: Stream,
{ {
type Item = T::Item;
fn into_result(self) -> Result<T, FieldError<S>> { fn into_result(self) -> Result<T, FieldError<S>> {
Ok(self) Ok(self)
} }

View file

@ -53,7 +53,8 @@ pub trait GraphQLUnion<S: ScalarValue>: GraphQLType<S> {
/// types. Each type which can be used as an output type should /// types. Each type which can be used as an output type should
/// implement this trait. The specification defines enum, scalar, /// implement this trait. The specification defines enum, scalar,
/// object, union, and interface as output types. /// object, union, and interface as output types.
pub trait IsOutputType<S: ScalarValue>: GraphQLType<S> { // TODO: Re-enable GraphQLType requirement in #682
pub trait IsOutputType<S: ScalarValue> /*: GraphQLType<S>*/ {
/// An arbitrary function without meaning. /// An arbitrary function without meaning.
/// ///
/// May contain compile timed check logic which ensures that types /// May contain compile timed check logic which ensures that types
@ -132,13 +133,13 @@ where
impl<S, T> IsInputType<S> for Box<T> impl<S, T> IsInputType<S> for Box<T>
where where
T: IsInputType<S>, T: IsInputType<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
{ {
} }
impl<S, T> IsOutputType<S> for Box<T> impl<S, T> IsOutputType<S> for Box<T>
where where
T: IsOutputType<S>, T: IsOutputType<S> + ?Sized,
S: ScalarValue, S: ScalarValue,
{ {
} }

View file

@ -195,6 +195,9 @@ fn impl_scalar_struct(
<#inner_ty as ::juniper::ParseScalarValue<S>>::from_str(value) <#inner_ty as ::juniper::ParseScalarValue<S>>::from_str(value)
} }
} }
impl<S: ::juniper::ScalarValue> ::juniper::marker::IsOutputType<S> for #ident { }
impl<S: ::juniper::ScalarValue> ::juniper::marker::IsInputType<S> for #ident { }
); );
Ok(content) Ok(content)

View file

@ -611,7 +611,7 @@ impl ToTokens for UnionDefinition {
#where_clause #where_clause
{ {
fn mark() { fn mark() {
#( <#var_types as ::juniper::marker::GraphQLObjectType<#scalar>>::mark(); )* #( <#var_types as ::juniper::marker::IsOutputType<#scalar>>::mark(); )*
} }
} }
}; };

View file

@ -831,12 +831,9 @@ impl GraphQLTypeDefiniton {
if self.scalar.is_none() && self.generic_scalar { if self.scalar.is_none() && self.generic_scalar {
// No custom scalar specified, but always generic specified. // No custom scalar specified, but always generic specified.
// Therefore we inject the generic scalar. // Therefore we inject the generic scalar.
generics.params.push(parse_quote!(__S)); generics.params.push(parse_quote!(__S));
generics
let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); .make_where_clause()
// Insert ScalarValue constraint.
where_clause
.predicates .predicates
.push(parse_quote!(__S: ::juniper::ScalarValue)); .push(parse_quote!(__S: ::juniper::ScalarValue));
} }
@ -962,26 +959,29 @@ impl GraphQLTypeDefiniton {
) )
}; };
// FIXME: enable this if interfaces are supported let marks = self.fields.iter().map(|field| {
// let marks = self.fields.iter().map(|field| { let field_marks = field.args.iter().map(|arg| {
// let field_ty = &field._type; let arg_ty = &arg._type;
quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }
});
// let field_marks = field.args.iter().map(|arg| { let field_ty = &field._type;
// let arg_ty = &arg._type; let resolved_ty = quote! {
// quote!(<#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) <#field_ty as ::juniper::IntoResolvable<
// }); '_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
>>::Type
};
// quote!( quote! {
// #( #field_marks)* #( #field_marks )*
// <#field_ty as ::juniper::marker::IsOutputType<#scalar>>::mark(); <#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
// ) }
// }); });
let output = quote!( let output = quote!(
impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause { impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens #where_clause {
fn mark() { fn mark() {
// FIXME: enable this if interfaces are supported #( #marks )*
// #( #marks )*
} }
} }
@ -1001,10 +1001,10 @@ impl GraphQLTypeDefiniton {
) -> ::juniper::meta::MetaType<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar>
where #scalar : 'r, where #scalar : 'r,
{ {
let fields = vec![ let fields = [
#( #field_definitions ),* #( #field_definitions ),*
]; ];
let meta = registry.build_object_type::<#ty>( info, &fields ) let meta = registry.build_object_type::<#ty>(info, &fields)
#description #description
#interfaces; #interfaces;
meta.into_meta() meta.into_meta()
@ -1226,7 +1226,37 @@ impl GraphQLTypeDefiniton {
}, },
); );
let marks = self.fields.iter().map(|field| {
let field_marks = field.args.iter().map(|arg| {
let arg_ty = &arg._type;
quote! { <#arg_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }
});
let field_ty = &field._type;
let stream_item_ty = quote! {
<#field_ty as ::juniper::IntoFieldResult::<_, #scalar>>::Item
};
let resolved_ty = quote! {
<#stream_item_ty as ::juniper::IntoResolvable<
'_, #scalar, _, <Self as ::juniper::GraphQLValue<#scalar>>::Context,
>>::Type
};
quote! {
#( #field_marks )*
<#resolved_ty as ::juniper::marker::IsOutputType<#scalar>>::mark();
}
});
let graphql_implementation = quote!( let graphql_implementation = quote!(
impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #type_generics_tokens
#where_clause
{
fn mark() {
#( #marks )*
}
}
impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #type_generics_tokens
#where_clause #where_clause
{ {
@ -1240,10 +1270,10 @@ impl GraphQLTypeDefiniton {
) -> ::juniper::meta::MetaType<'r, #scalar> ) -> ::juniper::meta::MetaType<'r, #scalar>
where #scalar : 'r, where #scalar : 'r,
{ {
let fields = vec![ let fields = [
#( #field_definitions ),* #( #field_definitions ),*
]; ];
let meta = registry.build_object_type::<#ty>( info, &fields ) let meta = registry.build_object_type::<#ty>(info, &fields)
#description #description
#interfaces; #interfaces;
meta.into_meta() meta.into_meta()
@ -1690,18 +1720,16 @@ impl GraphQLTypeDefiniton {
{} {}
); );
// FIXME: enable this if interfaces are supported let marks = self.fields.iter().map(|field| {
// let marks = self.fields.iter().map(|field| { let field_ty = &field._type;
// let _ty = &field._type; quote! { <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); }
// quote!(<#_ty as ::juniper::marker::IsInputType<#scalar>>::mark();) });
// });
let mut body = quote!( let mut body = quote!(
impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty #type_generics_tokens impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ty #type_generics_tokens
#where_clause { #where_clause {
fn mark() { fn mark() {
// FIXME: enable this if interfaces are supported #( #marks )*
// #( #marks )*
} }
} }