dynamic-graphql-derive 0.10.2

Dynamic GraphQL schema macro
Documentation
use dynamic_graphql::App;
use dynamic_graphql::FieldValue;
use dynamic_graphql::Instance;
use dynamic_graphql::Interface;
use dynamic_graphql::ResolvedObject;
use dynamic_graphql::ResolvedObjectFields;
use dynamic_graphql::SimpleObject;
use dynamic_graphql::dynamic::DynamicRequestExt;

use crate::schema_utils::normalize_schema;

#[tokio::test]
async fn test_interface_as_optional_value() {
    #[Interface]
    trait Node {
        fn the_id(&self) -> String;
    }

    #[derive(SimpleObject)]
    #[graphql(implements(Node))]
    struct FooNode {
        other_field: String,
    }

    impl Node for FooNode {
        fn the_id(&self) -> String {
            "foo".to_string()
        }
    }

    #[derive(ResolvedObject)]
    #[graphql(root)]
    struct Query;

    #[ResolvedObjectFields]
    impl Query {
        async fn node(&self) -> Option<Instance<'static, dyn Node>> {
            Some(Instance::new_owned(FooNode {
                other_field: "foo".to_string(),
            }))
        }
    }

    #[derive(App)]
    struct App(Query, FooNode);

    let schema = App::create_schema().finish().unwrap();

    let sdl = schema.sdl();
    insta::assert_snapshot!(normalize_schema(&sdl), @r#"
    type FooNode implements Node {
      otherField: String!
      theId: String!
    }

    interface Node {
      theId: String!
    }

    type Query {
      node: 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 {
            node {
                theId
                ... on FooNode {
                    otherField
                }
            }
        }

    "#;

    let root = Query;
    let req = dynamic_graphql::Request::new(query).root_value(FieldValue::owned_any(root));
    let res = schema.execute(req).await;
    assert_eq!(
        res.data.into_json().unwrap(),
        serde_json::json!({
            "node": {
                "theId": "foo",
                "otherField": "foo",
            }
        })
    );
}

#[tokio::test]
async fn test_interface_as_list_value() {
    #[Interface]
    trait Node {
        fn the_id(&self) -> String;
    }

    #[derive(SimpleObject)]
    #[graphql(implements(Node))]
    struct FooNode {
        other_field: String,
    }

    impl Node for FooNode {
        fn the_id(&self) -> String {
            "foo".to_string()
        }
    }

    #[derive(SimpleObject)]
    #[graphql(implements(Node))]
    struct BarNode {
        another_field: String,
    }

    impl Node for BarNode {
        fn the_id(&self) -> String {
            "foo".to_string()
        }
    }

    #[derive(ResolvedObject)]
    #[graphql(root)]
    struct Query;

    #[ResolvedObjectFields]
    impl Query {
        async fn nodes(&self) -> Vec<Instance<'static, dyn Node>> {
            vec![
                Instance::new_owned(FooNode {
                    other_field: "foo".to_string(),
                }),
                Instance::new_owned(BarNode {
                    another_field: "bar".to_string(),
                }),
            ]
        }
    }

    #[derive(App)]
    struct App(Query, FooNode, BarNode);

    let schema = App::create_schema().finish().unwrap();

    let sdl = schema.sdl();
    insta::assert_snapshot!(normalize_schema(&sdl), @r#"
    type BarNode implements Node {
      anotherField: String!
      theId: String!
    }

    type FooNode implements Node {
      otherField: String!
      theId: String!
    }

    interface Node {
      theId: String!
    }

    type Query {
      nodes: [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 {
            nodes {
                theId
                ... on FooNode {
                    otherField
                }
                ... on BarNode {
                    anotherField
                }
            }
        }

    "#;

    let root = Query;
    let req = dynamic_graphql::Request::new(query).root_value(FieldValue::owned_any(root));
    let res = schema.execute(req).await;
    assert_eq!(
        res.data.into_json().unwrap(),
        serde_json::json!({
            "nodes": [
                {
                    "theId": "foo",
                    "otherField": "foo",
                },
                {
                    "theId": "foo",
                    "anotherField": "bar",
                },
            ]
        })
    );
}