Add entity lookup support for MergedObject.

Add some GraphQL specification constraints for all derived macros.

Use `Registry::create_dummy_type` to create a merged type.
This commit is contained in:
Sunli 2020-10-20 11:49:31 +08:00
parent b5e602342d
commit e3d693da28
26 changed files with 300 additions and 82 deletions

View File

@ -83,6 +83,8 @@ pub struct SimpleObject {
#[darling(default)]
pub internal: bool,
#[darling(default)]
pub dummy: bool,
#[darling(default)]
pub name: Option<String>,
#[darling(default)]
pub rename_fields: Option<RenameRule>,

View File

@ -111,6 +111,14 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
None
};
if schema_enum_items.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Enum type must define one or more unique enum values.",
)
.into());
}
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::resolver_utils::EnumType for #ident {

View File

@ -67,9 +67,9 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
schema_fields.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::InputObjectType);
#ty::create_type_info(registry);
if let Some(#crate_name::registry::MetaType::InputObject { input_fields, .. }) =
registry.types.get(&*<#ty as #crate_name::Type>::type_name()) {
fields.extend(::std::clone::Clone::clone(input_fields));
if let #crate_name::registry::MetaType::InputObject { input_fields, .. } =
registry.create_dummy_type::<#ty>() {
fields.extend(input_fields);
}
});
@ -151,6 +151,14 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
})
}
if get_fields.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Input Object type must define one or more input fields.",
)
.into());
}
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::Type for #ident {

View File

@ -121,6 +121,14 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
let mut schema_fields = Vec::new();
let mut resolvers = Vec::new();
if interface_args.fields.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Interface type must define one or more fields.",
)
.into());
}
for InterfaceField {
name,
method,

View File

@ -62,18 +62,16 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| {
#merged_type::create_type_info(registry);
let mut fields = ::std::default::Default::default();
let mut cache_control = ::std::default::Default::default();
if let ::std::option::Option::Some(#crate_name::registry::MetaType::Object {
if let #crate_name::registry::MetaType::Object {
fields: obj_fields,
cache_control: obj_cache_control,
..
}) = registry.types.get(&*#merged_type::type_name()) {
fields = ::std::clone::Clone::clone(obj_fields);
cache_control = *obj_cache_control;
} = registry.create_dummy_type::<#merged_type>() {
fields = obj_fields;
cache_control = obj_cache_control;
}
#crate_name::registry::MetaType::Object {
@ -94,6 +92,10 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#create_merged_obj.resolve_field(ctx).await
}
async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#create_merged_obj.find_entity(ctx, params).await
}
}
#[allow(clippy::all, clippy::pedantic)]

View File

@ -53,15 +53,13 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
registry.create_type::<Self, _>(|registry| {
#merged_type::create_type_info(registry);
let mut fields = ::std::default::Default::default();
if let ::std::option::Option::Some(#crate_name::registry::MetaType::Object {
if let #crate_name::registry::MetaType::Object {
fields: obj_fields,
..
}) = registry.types.get(&*#merged_type::type_name()) {
fields = ::std::clone::Clone::clone(obj_fields);
} = registry.create_dummy_type::<#merged_type>() {
fields = obj_fields;
}
#crate_name::registry::MetaType::Object {

View File

@ -471,6 +471,14 @@ pub fn generate(
find_entities.sort_by(|(a, _), (b, _)| b.cmp(a));
let find_entities_iter = find_entities.iter().map(|(_, code)| code);
if resolvers.is_empty() && create_entity_types.is_empty() {
return Err(Error::new_spanned(
&self_ty,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let expanded = quote! {
#item_impl

View File

@ -129,6 +129,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
});
}
if !object_args.dummy && resolvers.is_empty() {
return Err(Error::new_spanned(
&ident,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let cache_control = {
let public = object_args.cache_control.is_public();
let max_age = object_args.cache_control.max_age;
@ -181,7 +189,6 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await
}

View File

@ -364,6 +364,14 @@ pub fn generate(
}
}
if create_stream.is_empty() {
return Err(Error::new_spanned(
&self_ty,
"An GraphQL Object type must define one or more fields.",
)
.into());
}
let expanded = quote! {
#item_impl

View File

@ -106,19 +106,18 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
});
}
registry_types.push(quote! {
<#p as #crate_name::Type>::create_type_info(registry);
});
if !variant.flatten {
registry_types.push(quote! {
<#p as #crate_name::Type>::create_type_info(registry);
});
possible_types.push(quote! {
possible_types.insert(<#p as #crate_name::Type>::type_name().into_owned());
});
} else {
possible_types.push(quote! {
if let Some(#crate_name::registry::MetaType::Union { possible_types: possible_types2, .. }) =
registry.types.get(&*<#p as #crate_name::Type>::type_name()) {
possible_types.extend(::std::clone::Clone::clone(possible_types2));
if let #crate_name::registry::MetaType::Union { possible_types: possible_types2, .. } =
registry.create_dummy_type::<#p>() {
possible_types.extend(possible_types2);
}
});
}
@ -141,6 +140,14 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
}
}
if possible_types.is_empty() {
return Err(Error::new_spanned(
&ident,
"A GraphQL Union type must include one or more unique member types.",
)
.into());
}
let expanded = quote! {
#(#type_into_impls)*

View File

@ -142,8 +142,13 @@ async fn upload() -> Result<()> {
async_std::task::spawn(async move {
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
#[derive(Clone, SimpleObject)]
pub struct FileInfo {

View File

@ -17,7 +17,12 @@ use warp::{Filter, Rejection, Reply};
/// struct QueryRoot;
///
/// #[Object]
/// impl QueryRoot {}
/// impl QueryRoot {
/// async fn value(&self) -> i32 {
/// // An GraphQL Object type must define one or more fields.
/// 100
/// }
/// }
///
/// struct SubscriptionRoot;
///

View File

@ -254,6 +254,7 @@ pub struct MetaDirective {
pub args: IndexMap<&'static str, MetaInputValue>,
}
#[derive(Default)]
pub struct Registry {
pub types: IndexMap<String, MetaType>,
pub directives: HashMap<String, MetaDirective>,
@ -288,6 +289,18 @@ impl Registry {
T::qualified_type_name()
}
pub fn create_dummy_type<T: crate::Type>(&mut self) -> MetaType {
let mut dummy_registry = Registry::default();
T::create_type_info(&mut dummy_registry);
if let Some(ty) = dummy_registry.types.remove(&*T::type_name()) {
self.types.extend(dummy_registry.types);
self.implements.extend(dummy_registry.implements);
ty
} else {
unreachable!()
}
}
pub fn add_directive(&mut self, directive: MetaDirective) {
self.directives
.insert(directive.name.to_string(), directive);

View File

@ -55,6 +55,10 @@ impl<T: ContainerType + Send + Sync> ContainerType for &T {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(*self, ctx).await
}
async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
T::find_entity(*self, ctx, params).await
}
}
/// Resolve an container by executing each of the fields concurrently.

View File

@ -70,9 +70,15 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
/// ```rust
/// use async_graphql::*;
///
/// #[derive(SimpleObject)]
/// struct Query;
///
/// #[Object]
/// impl Query {
/// async fn value(&self) -> i32 {
/// 100
/// }
/// }
///
/// let schema = Schema::build(Query, EmptyMutation,EmptySubscription)
/// .extension(extensions::Logger)
/// .finish();

View File

@ -52,11 +52,10 @@ where
fn create_type_info(registry: &mut registry::Registry) -> String {
registry.create_type::<Self, _>(|registry| {
E::create_type_info(registry);
let additional_fields = if let Some(registry::MetaType::Object { fields, .. }) =
registry.types.get(E::type_name().as_ref())
let additional_fields = if let registry::MetaType::Object { fields, .. } =
registry.create_dummy_type::<E>()
{
fields.clone()
fields
} else {
unreachable!()
};

View File

@ -16,7 +16,7 @@ pub use page_info::PageInfo;
/// Empty additional fields
#[derive(SimpleObject)]
#[graphql(internal)]
#[graphql(internal, dummy)]
pub struct EmptyFields;
/// Parses the parameters and executes the query.

View File

@ -19,7 +19,12 @@ use crate::{
/// struct QueryRoot;
///
/// #[Object]
/// impl QueryRoot {}
/// impl QueryRoot {
/// async fn value(&self) -> i32 {
/// // An GraphQL Object type must define one or more fields.
/// 100
/// }
/// }
///
/// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
/// ```

View File

@ -9,6 +9,7 @@ use crate::{
CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType,
Positioned, ServerResult, SimpleObject, Type, Value,
};
use async_graphql_value::ConstValue;
#[doc(hidden)]
pub struct MergedObject<A, B>(pub A, pub B);
@ -23,25 +24,23 @@ impl<A: Type, B: Type> Type for MergedObject<A, B> {
let mut fields = IndexMap::new();
let mut cc = CacheControl::default();
A::create_type_info(registry);
if let Some(MetaType::Object {
if let MetaType::Object {
fields: a_fields,
cache_control: a_cc,
..
}) = registry.types.get(&*A::type_name())
} = registry.create_dummy_type::<A>()
{
fields.extend(a_fields.clone());
fields.extend(a_fields);
cc = cc.merge(&a_cc);
}
B::create_type_info(registry);
if let Some(MetaType::Object {
if let MetaType::Object {
fields: b_fields,
cache_control: b_cc,
..
}) = registry.types.get(&*B::type_name())
} = registry.create_dummy_type::<B>()
{
fields.extend(b_fields.clone());
fields.extend(b_fields);
cc = cc.merge(&b_cc);
}
@ -70,6 +69,18 @@ where
Err(err) => Err(err),
}
}
async fn find_entity(
&self,
ctx: &Context<'_>,
params: &ConstValue,
) -> ServerResult<Option<ConstValue>> {
match self.0.find_entity(ctx, params).await {
Ok(Some(value)) => Ok(Some(value)),
Ok(None) => self.1.find_entity(ctx, params).await,
Err(err) => Err(err),
}
}
}
#[async_trait::async_trait]
@ -96,5 +107,5 @@ where
#[doc(hidden)]
#[derive(SimpleObject, Default)]
#[graphql(internal)]
#[graphql(internal, dummy)]
pub struct MergedObjectTail;

View File

@ -185,7 +185,11 @@ pub async fn test_merged_subscription() {
struct Query;
#[Object]
impl Query {}
impl Query {
async fn value(&self) -> i32 {
10
}
}
let schema = Schema::new(Query, EmptyMutation, Subscription::default());
@ -221,3 +225,68 @@ pub async fn test_merged_subscription() {
assert!(stream.next().await.is_none());
}
}
#[async_std::test]
pub async fn test_merged_entity() {
#[derive(SimpleObject)]
struct Fruit {
id: ID,
name: String,
}
#[derive(SimpleObject)]
struct Vegetable {
id: ID,
name: String,
}
#[derive(Default)]
struct FruitQuery;
#[Object]
impl FruitQuery {
#[graphql(entity)]
async fn get_fruit(&self, id: ID) -> Fruit {
Fruit {
id,
name: "Apple".into(),
}
}
}
#[derive(Default)]
struct VegetableQuery;
#[Object]
impl VegetableQuery {
#[graphql(entity)]
async fn get_vegetable(&self, id: ID) -> Vegetable {
Vegetable {
id,
name: "Carrot".into(),
}
}
}
#[derive(MergedObject, Default)]
struct Query(FruitQuery, VegetableQuery);
let schema = Schema::new(Query::default(), EmptyMutation, EmptySubscription);
let query = r#"{
_entities(representations: [{__typename: "Fruit", id: "1"}]) {
__typename
... on Fruit {
id
name
}
}
}"#;
assert_eq!(
schema.execute(query).await.into_result().unwrap().data,
value!({
"_entities": [
{"__typename": "Fruit", "id": "1", "name": "Apple"},
]
})
);
}

View File

@ -7,9 +7,15 @@ use std::time::Duration;
pub async fn test_mutation_execution_order() {
type List = Arc<Mutex<Vec<i32>>>;
#[derive(SimpleObject)]
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MutationRoot;
#[Object]
@ -38,9 +44,15 @@ pub async fn test_mutation_execution_order() {
#[async_std::test]
pub async fn test_mutation_fragment() {
#[derive(SimpleObject)]
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MutationRoot;
#[Object]

View File

@ -105,9 +105,15 @@ pub async fn test_input_object() {
#[async_std::test]
pub async fn test_subscription() {
#[derive(SimpleObject)]
struct Query;
#[Object]
impl Query {
async fn value(&self) -> i32 {
10
}
}
struct Subscription;
#[Subscription(rename_fields = "SCREAMING_SNAKE_CASE", rename_args = "lowercase")]

View File

@ -2,10 +2,17 @@ use async_graphql::*;
#[async_std::test]
pub async fn test_schema_default() {
#[derive(SimpleObject, Default)]
struct Query;
#[derive(Default)]
struct QueryRoot;
type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
type MySchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
let _schema = MySchema::default();
}

View File

@ -1,19 +1,23 @@
use async_graphql::*;
use futures_util::stream::{Stream, StreamExt, TryStreamExt};
struct QueryRoot;
#[Object]
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
#[async_std::test]
pub async fn test_subscription() {
struct QueryRoot;
#[derive(SimpleObject)]
struct Event {
a: i32,
b: i32,
}
#[Object]
impl QueryRoot {}
struct SubscriptionRoot;
#[Subscription]
@ -60,7 +64,11 @@ pub async fn test_subscription_with_ctx_data() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct MyObject;
@ -106,7 +114,11 @@ pub async fn test_subscription_with_token() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot;
@ -150,16 +162,20 @@ pub async fn test_subscription_with_token() {
#[async_std::test]
pub async fn test_subscription_inline_fragment() {
struct QueryRoot;
#[derive(SimpleObject)]
struct Event {
a: i32,
b: i32,
}
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot;
@ -197,8 +213,6 @@ pub async fn test_subscription_inline_fragment() {
#[async_std::test]
pub async fn test_subscription_fragment() {
struct QueryRoot;
#[derive(SimpleObject)]
struct Event {
a: i32,
@ -211,9 +225,6 @@ pub async fn test_subscription_fragment() {
Event(Event),
}
#[Object]
impl QueryRoot {}
struct SubscriptionRoot;
#[Subscription]
@ -252,8 +263,6 @@ pub async fn test_subscription_fragment() {
#[async_std::test]
pub async fn test_subscription_fragment2() {
struct QueryRoot;
#[derive(SimpleObject)]
struct Event {
a: i32,
@ -266,9 +275,6 @@ pub async fn test_subscription_fragment2() {
Event(Event),
}
#[Object]
impl QueryRoot {}
struct SubscriptionRoot;
#[Subscription]
@ -308,8 +314,6 @@ pub async fn test_subscription_fragment2() {
#[async_std::test]
pub async fn test_subscription_error() {
struct QueryRoot;
struct Event {
value: i32,
}
@ -325,9 +329,6 @@ pub async fn test_subscription_error() {
}
}
#[Object]
impl QueryRoot {}
struct SubscriptionRoot;
#[Subscription]
@ -370,11 +371,6 @@ pub async fn test_subscription_error() {
#[async_std::test]
pub async fn test_subscription_fieldresult() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
struct SubscriptionRoot;
#[Subscription]

View File

@ -6,7 +6,11 @@ pub async fn test_subscription_ws_transport() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot;
@ -77,7 +81,11 @@ pub async fn test_subscription_ws_transport_with_token() {
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot;
@ -161,8 +169,6 @@ pub async fn test_subscription_ws_transport_with_token() {
#[async_std::test]
pub async fn test_subscription_ws_transport_error() {
struct QueryRoot;
struct Event {
value: i32,
}
@ -178,8 +184,14 @@ pub async fn test_subscription_ws_transport_error() {
}
}
struct QueryRoot;
#[Object]
impl QueryRoot {}
impl QueryRoot {
async fn value(&self) -> i32 {
10
}
}
struct SubscriptionRoot;

View File

@ -327,6 +327,10 @@ pub async fn test_union_flatten() {
async fn value2(&self) -> MyUnion {
InnerUnion2::B(MyObj2 { value2: 88 }).into()
}
async fn value3(&self) -> InnerUnion1 {
InnerUnion1::A(MyObj1 { value1: 77 })
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
@ -342,6 +346,11 @@ pub async fn test_union_flatten() {
value2
}
}
value3 {
... on MyObj1 {
value1
}
}
}"#;
assert_eq!(
schema.execute(query).await.into_result().unwrap().data,
@ -351,6 +360,9 @@ pub async fn test_union_flatten() {
},
"value2": {
"value2": 88,
},
"value3": {
"value1": 77,
}
})
);