use std::borrow::Cow;
use dynamic_graphql::App;
use dynamic_graphql::ExpandObject;
use dynamic_graphql::ExpandObjectFields;
use dynamic_graphql::Interface;
use dynamic_graphql::SimpleObject;
use dynamic_graphql::internal::Interface;
use dynamic_graphql::internal::Object;
use dynamic_graphql::internal::TypeName;
use crate::schema_utils::normalize_schema;
#[test]
fn test_impl_interface() {
#[Interface]
trait Node {
fn id(&self) -> String;
}
assert_eq!(<dyn Node as Interface>::get_interface_type_name(), "Node");
}
#[test]
fn test_impl_interface_with_name() {
#[Interface]
#[graphql(name = "Other")]
trait Node {
fn id(&self) -> String;
}
assert_eq!(<dyn Node as Interface>::get_interface_type_name(), "Other");
}
#[test]
fn test_schema() {
#[Interface]
trait Node {
fn the_id(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Node {
theId: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_schema_with_name() {
#[Interface]
#[graphql(name = "Other")]
trait Node {
#[graphql(name = "id")]
fn get_id(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Other {
id: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_schema_with_type_name() {
#[Interface]
#[graphql(get_type_name)]
trait Node {
fn the_id(&self) -> String;
}
impl TypeName for dyn Node {
fn get_type_name() -> Cow<'static, str> {
"Other".into()
}
}
#[derive(SimpleObject)]
#[graphql(mark(Node))]
struct FooNode {
the_id: String,
}
#[derive(SimpleObject)]
#[graphql(implements(Node))]
struct BarNode {
#[graphql(skip)]
the_id: String,
}
impl Node for BarNode {
fn the_id(&self) -> String {
self.the_id.clone()
}
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, FooNode, BarNode, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
type BarNode implements Other {
theId: String!
}
type FooNode implements Other {
theId: String!
}
interface Other {
theId: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_schema_with_rename() {
#[Interface]
#[graphql(rename_fields = "snake_case")]
trait Node {
#[graphql(name = "id")]
fn get_id(&self) -> String;
fn the_id(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Node {
id: String!
the_id: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_schema_description() {
#[Interface]
trait Node {
fn the_id(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
"the interface"
interface Node {
"the id"
theId: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_arg_descriptions_via_attr_and_doc_comment() {
#[Interface]
trait Node {
fn greet_via_doc(&self, name: String, times: i32) -> String;
fn greet_via_attr(
&self,
#[graphql(desc = "the salutation, e.g. 'hi' or 'hello'")] greeting: String,
) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let sdl = App::create_schema().finish().unwrap().sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Node {
"""
Greeter.
* `name` - the name of the person to greet.
* `times` - how many times to repeat.
"""
greetViaDoc(name: String!, times: Int!): String!
"""
`desc` attribute wins over a generic doc comment when both are
present (only the `desc` attribute is set here so we can verify
it lands in the schema).
"""
greetViaAttr("the salutation, e.g. 'hi' or 'hello'" greeting: String!): String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
fn has_arg_desc(sdl: &str, arg_name: &str, expected_desc: &str) -> bool {
sdl.lines().collect::<Vec<_>>().windows(4).any(|w| {
w[0].trim() == "\"\"\""
&& w[1].trim() == expected_desc
&& w[2].trim() == "\"\"\""
&& w[3].trim().starts_with(arg_name)
})
}
assert!(
has_arg_desc(&sdl, "greeting", "the salutation, e.g. 'hi' or 'hello'"),
"expected greeting arg description in SDL:\n{sdl}"
);
}
#[test]
fn test_schema_with_deprecation() {
#[Interface]
trait Node {
#[graphql(deprecation)]
fn the_id(&self) -> String;
#[graphql(deprecation = "deprecated")]
fn old(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Node {
theId: String! @deprecated
old: String! @deprecated(reason: "deprecated")
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[test]
fn test_schema_with_skip() {
#[Interface]
trait Node {
fn the_id(&self) -> String;
#[allow(dead_code)]
#[graphql(skip)]
fn old(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(root)]
struct Query {
foo: String,
}
#[derive(App)]
struct App(Query, dyn Node);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
interface Node {
theId: String!
}
type Query {
foo: String!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[tokio::test]
async fn test_auto_register() {
#[allow(dead_code)]
#[derive(SimpleObject)]
struct Bar {
id: String,
}
#[derive(SimpleObject)]
struct Foo {
id: String,
}
#[Interface]
#[graphql(register(Bar))]
trait GetFoo {
fn get_foo(&self) -> Foo;
}
#[derive(SimpleObject)]
#[graphql(implements(GetFoo))]
#[graphql(root)]
struct Query;
impl GetFoo for Query {
fn get_foo(&self) -> Foo {
Foo {
id: "foo".to_string(),
}
}
}
#[derive(App)]
struct App(Query);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
type Bar {
id: String!
}
type Foo {
id: String!
}
interface GetFoo {
getFoo: Foo!
}
type Query implements GetFoo {
getFoo: Foo!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
#[tokio::test]
async fn test_auto_register_instance() {
#[derive(ExpandObject)]
struct WithName<'a, T>(&'a T)
where
T: GetFoo + Object + 'static;
#[ExpandObjectFields]
impl<T> WithName<'_, T>
where
T: GetFoo + Object + 'static,
{
fn name(&self) -> String {
self.0.get_name()
}
}
#[allow(dead_code)]
#[derive(SimpleObject)]
struct Bar {
id: String,
}
#[derive(SimpleObject)]
struct Foo {
id: String,
}
#[Interface]
#[graphql(auto_register(WithName))]
trait GetFoo {
fn get_foo(&self) -> Foo;
#[graphql(skip)]
fn get_name(&self) -> String;
}
#[derive(SimpleObject)]
#[graphql(implements(GetFoo))]
#[graphql(root)]
struct Query;
impl GetFoo for Query {
fn get_foo(&self) -> Foo {
Foo {
id: "foo".to_string(),
}
}
fn get_name(&self) -> String {
"name".to_string()
}
}
#[derive(App)]
struct App(Query);
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
type Foo {
id: String!
}
interface GetFoo {
getFoo: Foo!
}
type Query implements GetFoo {
name: String!
getFoo: Foo!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
}
mod in_mod {
mod node {
use dynamic_graphql::Interface;
#[Interface]
pub trait Node {
fn id(&self) -> String;
}
}
mod foo {
use dynamic_graphql::FieldValue;
use dynamic_graphql::Instance;
use dynamic_graphql::ResolvedObject;
use dynamic_graphql::ResolvedObjectFields;
use dynamic_graphql::SimpleObject;
use dynamic_graphql::dynamic::DynamicRequestExt;
use crate::schema_utils::normalize_schema;
#[derive(SimpleObject)]
#[graphql(mark(super::node::Node))]
struct Bar {
id: String,
other: String,
}
#[derive(SimpleObject)]
#[graphql(implements(super::node::Node))]
struct Foo {
other: String,
}
impl super::node::Node for Foo {
fn id(&self) -> String {
"foo".to_string()
}
}
#[derive(ResolvedObject)]
#[graphql(root)]
pub struct Query;
#[ResolvedObjectFields]
impl Query {
async fn foo(&self) -> Instance<'static, dyn super::node::Node> {
Instance::new_owned(Foo {
other: "foo".to_string(),
})
}
async fn bar(&self) -> Instance<'static, dyn super::node::Node> {
Instance::new_owned(Bar {
id: "bar".to_string(),
other: "bar".to_string(),
})
}
}
#[derive(dynamic_graphql::App)]
pub struct App(Query, Bar, Foo);
#[tokio::test]
async fn test_in_mode() {
let schema = App::create_schema().finish().unwrap();
let sdl = schema.sdl();
insta::assert_snapshot!(normalize_schema(&sdl), @r#"
type Bar implements Node {
id: String!
other: String!
}
type Foo implements Node {
other: String!
id: String!
}
interface Node {
id: String!
}
type Query {
foo: Node!
bar: Node!
}
"Directs the executor to include this field or fragment only when the `if` argument is true."
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"Directs the executor to skip this field or fragment when the `if` argument is true."
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
schema {
query: Query
}
"#);
let query = r#"
query {
foo {
id
... on Foo {
other
}
}
bar {
id
... on Bar {
other
}
}
}
"#;
let root = Query;
let req = dynamic_graphql::Request::new(query).root_value(FieldValue::owned_any(root));
let res = schema.execute(req).await;
let data = res.data.into_json().unwrap();
assert_eq!(
data,
serde_json::json!({
"foo": { "id": "foo", "other": "foo" },
"bar": { "id": "bar", "other": "bar" },
})
);
}
}
}