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
--> $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
--> $DIR/impl_argument_no_object.rs:8:1
|

View file

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

View file

@ -3,7 +3,7 @@ use juniper::DefaultScalarValue;
use juniper::Object;
#[cfg(test)]
use juniper::{self, execute, EmptyMutation, EmptySubscription, RootNode, Value, Variables};
use juniper::{execute, EmptyMutation, EmptySubscription, FieldError, RootNode, Value, Variables};
pub struct MyObject;
@ -84,3 +84,16 @@ where
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>,
S: ScalarValue,
{
type Type;
#[doc(hidden)]
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S>;
}
@ -263,6 +265,8 @@ where
S: ScalarValue,
T::Context: FromContext<C>,
{
type Type = T;
fn into(self, ctx: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some((FromContext::from(ctx), self)))
}
@ -274,6 +278,8 @@ where
T: GraphQLValue<S>,
T::Context: FromContext<C>,
{
type Type = T;
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)))
.map_err(IntoFieldError::into_field_error)
@ -285,6 +291,8 @@ where
S: ScalarValue,
T: GraphQLValue<S>,
{
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
Ok(Some(self))
}
@ -295,6 +303,8 @@ where
S: ScalarValue,
T: GraphQLValue<S>,
{
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> {
Ok(self.map(|(ctx, v)| (ctx, Some(v))))
}
@ -305,6 +315,8 @@ where
S: ScalarValue,
T: GraphQLValue<S>,
{
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, T)>, S> {
self.map(Some)
}
@ -316,6 +328,8 @@ where
S: ScalarValue,
T: GraphQLValue<S>,
{
type Type = T;
fn into(self, _: &'a C) -> FieldResult<Option<(&'a T::Context, Option<T>)>, S> {
self.map(|o| o.map(|(ctx, v)| (ctx, Some(v))))
}

View file

@ -183,7 +183,7 @@ pub use crate::{
types::{
async_await::{GraphQLTypeAsync, GraphQLValueAsync},
base::{Arguments, GraphQLType, GraphQLValue, TypeKind},
marker::{self, GraphQLUnion},
marker::{self, GraphQLUnion, IsOutputType},
scalars::{EmptyMutation, EmptySubscription, ID},
subscriptions::{
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!(
impl<$($scalar)* $(, $lifetimes)* > GraphQLValue for $name {
type Context = $ctx;

View file

@ -7,27 +7,36 @@ use futures::Stream;
use crate::{FieldError, GraphQLValue, ScalarValue};
/// Trait for converting `T` to `Ok(T)` if T is 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.
/// Trait for wrapping [`Stream`] into [`Ok`] if it's not [`Result`].
///
/// 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> {
/// 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>>;
}
impl<T, E, S> IntoFieldResult<T, S> for Result<T, E>
where
T: IntoFieldResult<T, S>,
E: Into<FieldError<S>>,
{
type Item = T::Item;
fn into_result(self) -> Result<T, FieldError<S>> {
self.map_err(|e| e.into())
}
}
impl<T, I, S> IntoFieldResult<T, S> for T
impl<T, S> IntoFieldResult<T, S> for T
where
T: Stream<Item = I>,
T: Stream,
{
type Item = T::Item;
fn into_result(self) -> Result<T, FieldError<S>> {
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
/// implement this trait. The specification defines enum, scalar,
/// 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.
///
/// May contain compile timed check logic which ensures that types
@ -132,13 +133,13 @@ where
impl<S, T> IsInputType<S> for Box<T>
where
T: IsInputType<S>,
T: IsInputType<S> + ?Sized,
S: ScalarValue,
{
}
impl<S, T> IsOutputType<S> for Box<T>
where
T: IsOutputType<S>,
T: IsOutputType<S> + ?Sized,
S: ScalarValue,
{
}

View file

@ -195,6 +195,9 @@ fn impl_scalar_struct(
<#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)

View file

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