If a GraphQL name conflict is detected when creating schema, it will cause panic. #499

This commit is contained in:
Sunli 2021-11-08 09:33:20 +08:00
parent ff994dc1ec
commit b359b62976
17 changed files with 308 additions and 17 deletions

View File

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
- Fix the problem that `EmptyMutation` may cause when used in `MergedObject`. [#694](https://github.com/async-graphql/async-graphql/issues/694)
- If a GraphQL name conflict is detected when creating schema, it will cause panic. [#499](https://github.com/async-graphql/async-graphql/issues/499)
## [2.11.1] 2021-11-07

View File

@ -140,6 +140,7 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
enum_items
},
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
}
})
}

View File

@ -215,6 +215,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
fields
},
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
})
}
}
@ -259,6 +260,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
fields
},
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
})
}

View File

@ -350,6 +350,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
}
})
}

View File

@ -91,6 +91,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
rust_typename: ::std::any::type_name::<Self>(),
}
})
}

View File

@ -73,6 +73,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: true,
rust_typename: ::std::any::type_name::<Self>(),
}
})
}

View File

@ -564,6 +564,7 @@ pub fn generate(
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
rust_typename: ::std::any::type_name::<Self>(),
});
#(#create_entity_types)*
#(#add_keys)*
@ -632,6 +633,7 @@ pub fn generate(
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
rust_typename: ::std::any::type_name::<Self>(),
});
#(#create_entity_types)*
#(#add_keys)*

View File

@ -294,6 +294,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
rust_typename: ::std::any::type_name::<Self>(),
})
}
}
@ -345,6 +346,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
rust_typename: ::std::any::type_name::<Self>(),
})
}

View File

@ -457,6 +457,7 @@ pub fn generate(
keys: ::std::option::Option::None,
visible: ::std::option::Option::None,
is_subscription: true,
rust_typename: ::std::any::type_name::<Self>(),
})
}
}

View File

@ -193,6 +193,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
union_values
},
visible: #visible,
rust_typename: ::std::any::type_name::<Self>(),
}
})
}

View File

@ -200,6 +200,7 @@ pub enum MetaType {
keys: Option<Vec<String>>,
visible: Option<MetaVisibleFn>,
is_subscription: bool,
rust_typename: &'static str,
},
Interface {
name: String,
@ -209,6 +210,7 @@ pub enum MetaType {
extends: bool,
keys: Option<Vec<String>>,
visible: Option<MetaVisibleFn>,
rust_typename: &'static str,
},
Union {
name: String,
@ -216,18 +218,21 @@ pub enum MetaType {
union_values: IndexMap<String, MetaUnionValue>,
possible_types: IndexSet<String>,
visible: Option<MetaVisibleFn>,
rust_typename: &'static str,
},
Enum {
name: String,
description: Option<&'static str>,
enum_values: IndexMap<&'static str, MetaEnumValue>,
visible: Option<MetaVisibleFn>,
rust_typename: &'static str,
},
InputObject {
name: String,
description: Option<&'static str>,
input_fields: IndexMap<String, MetaInputValue>,
visible: Option<MetaVisibleFn>,
rust_typename: &'static str,
},
}
@ -336,6 +341,17 @@ impl MetaType {
(false, false) => false,
}
}
pub fn rust_typename(&self) -> Option<&'static str> {
match self {
MetaType::Scalar { .. } => None,
MetaType::Object { rust_typename, .. } => Some(rust_typename),
MetaType::Interface { rust_typename, .. } => Some(rust_typename),
MetaType::Union { rust_typename, .. } => Some(rust_typename),
MetaType::Enum { rust_typename, .. } => Some(rust_typename),
MetaType::InputObject { rust_typename, .. } => Some(rust_typename),
}
}
}
pub struct MetaDirective {
@ -364,24 +380,40 @@ impl Registry {
mut f: F,
) -> String {
let name = T::type_name();
if !self.types.contains_key(name.as_ref()) {
// Inserting a fake type before calling the function allows recursive types to exist.
self.types.insert(
name.clone().into_owned(),
MetaType::Object {
name: "".to_string(),
description: None,
fields: Default::default(),
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
is_subscription: false,
},
);
let ty = f(self);
*self.types.get_mut(&*name).unwrap() = ty;
match self.types.get(name.as_ref()) {
Some(ty) => {
let rust_typename = std::any::type_name::<T>();
if let Some(prev_typename) = ty.rust_typename() {
if prev_typename != "__fake_type__" && rust_typename != prev_typename {
panic!(
"`{}` and `{}` have the same GraphQL name `{}`",
prev_typename, rust_typename, name,
);
}
}
}
None => {
// Inserting a fake type before calling the function allows recursive types to exist.
self.types.insert(
name.clone().into_owned(),
MetaType::Object {
name: "".to_string(),
description: None,
fields: Default::default(),
cache_control: Default::default(),
extends: false,
keys: None,
visible: None,
is_subscription: false,
rust_typename: "__fake_type__",
},
);
let ty = f(self);
*self.types.get_mut(&*name).unwrap() = ty;
}
}
T::qualified_type_name()
}
@ -474,6 +506,7 @@ impl Registry {
union_values: Default::default(),
possible_types,
visible: None,
rust_typename: "async_graphql::federation::Entity",
},
);
}
@ -511,6 +544,7 @@ impl Registry {
keys: None,
visible: None,
is_subscription: false,
rust_typename: "async_graphql::federation::Service",
},
);

View File

@ -191,6 +191,7 @@ where
keys: None,
visible: None,
is_subscription: false,
rust_typename: std::any::type_name::<Self>(),
}
})
}

View File

@ -108,6 +108,7 @@ where
keys: None,
visible: None,
is_subscription: false,
rust_typename: std::any::type_name::<Self>(),
}
})
}

View File

@ -46,6 +46,7 @@ impl Type for EmptyMutation {
keys: None,
visible: None,
is_subscription: false,
rust_typename: std::any::type_name::<Self>(),
})
}
}

View File

@ -26,6 +26,7 @@ impl Type for EmptySubscription {
keys: None,
visible: None,
is_subscription: true,
rust_typename: std::any::type_name::<Self>(),
})
}
}

View File

@ -51,6 +51,7 @@ impl<A: Type, B: Type> Type for MergedObject<A, B> {
keys: None,
visible: None,
is_subscription: false,
rust_typename: std::any::type_name::<Self>(),
}
})
}

239
tests/name_conflict.rs Normal file
View File

@ -0,0 +1,239 @@
use async_graphql::*;
#[test]
#[should_panic]
fn object() {
mod t {
use async_graphql::*;
pub struct MyObj;
#[Object]
impl MyObj {
async fn a(&self) -> i32 {
1
}
}
}
struct MyObj;
#[Object]
impl MyObj {
async fn b(&self) -> i32 {
1
}
}
#[derive(SimpleObject)]
struct Query {
a: MyObj,
b: t::MyObj,
}
Schema::new(
Query {
a: MyObj,
b: t::MyObj,
},
EmptyMutation,
EmptySubscription,
);
}
#[test]
#[should_panic]
fn simple_object() {
mod t {
use async_graphql::*;
#[derive(SimpleObject, Default)]
pub struct MyObj {
a: i32,
}
}
#[derive(SimpleObject, Default)]
struct MyObj {
a: i32,
}
#[derive(SimpleObject)]
struct Query {
a: MyObj,
b: t::MyObj,
}
Schema::new(
Query {
a: MyObj::default(),
b: t::MyObj::default(),
},
EmptyMutation,
EmptySubscription,
);
}
#[test]
#[should_panic]
fn merged_object() {
mod t {
use async_graphql::*;
#[derive(SimpleObject, Default)]
pub struct Query {
a: i32,
}
}
#[derive(SimpleObject, Default)]
struct Query {
a: i32,
}
#[derive(MergedObject)]
struct QueryRoot(Query, t::Query);
Schema::new(
QueryRoot(Query::default(), t::Query::default()),
EmptyMutation,
EmptySubscription,
);
}
#[test]
#[should_panic]
fn enum_type() {
mod t {
use async_graphql::*;
#[derive(Enum, Eq, PartialEq, Copy, Clone)]
pub enum MyEnum {
A,
}
}
#[derive(Enum, Eq, PartialEq, Copy, Clone)]
enum MyEnum {
B,
}
#[derive(SimpleObject)]
struct Query {
a: MyEnum,
b: t::MyEnum,
}
Schema::new(
Query {
a: MyEnum::B,
b: t::MyEnum::A,
},
EmptyMutation,
EmptySubscription,
);
}
#[test]
#[should_panic]
fn union() {
mod t {
use async_graphql::*;
#[derive(SimpleObject, Default)]
pub struct ObjA {
a: i32,
}
#[derive(SimpleObject, Default)]
pub struct ObjB {
a: i32,
}
#[derive(SimpleObject, Default)]
pub struct ObjC {
a: i32,
}
#[derive(Union)]
pub enum MyUnion {
ObjA(ObjA),
ObjB(ObjB),
}
}
#[derive(Union)]
pub enum MyUnion {
ObjA(t::ObjA),
ObjB(t::ObjB),
ObjC(t::ObjC),
}
#[derive(SimpleObject)]
struct Query {
a: MyUnion,
b: t::MyUnion,
}
Schema::new(
Query {
a: MyUnion::ObjA(t::ObjA::default()),
b: t::MyUnion::ObjB(t::ObjB::default()),
},
EmptyMutation,
EmptySubscription,
);
}
#[test]
#[should_panic]
fn interface() {
mod t {
use async_graphql::*;
#[derive(SimpleObject, Default)]
pub struct ObjA {
pub a: i32,
}
#[derive(SimpleObject, Default)]
pub struct ObjB {
pub a: i32,
}
#[derive(SimpleObject, Default)]
pub struct ObjC {
pub a: i32,
}
#[derive(Interface)]
#[graphql(field(name = "a", type = "&i32"))]
pub enum MyInterface {
ObjA(ObjA),
ObjB(ObjB),
ObjC(ObjC),
}
}
#[derive(Interface)]
#[graphql(field(name = "a", type = "&i32"))]
enum MyInterface {
ObjA(t::ObjA),
ObjB(t::ObjB),
}
#[derive(SimpleObject)]
struct Query {
a: MyInterface,
b: t::MyInterface,
}
Schema::new(
Query {
a: MyInterface::ObjA(t::ObjA::default()),
b: t::MyInterface::ObjB(t::ObjB::default()),
},
EmptyMutation,
EmptySubscription,
);
}