async-graphql 5.0.5

A GraphQL server library implemented in Rust
Documentation
use async_graphql::*;

#[tokio::test]
pub async fn test_input_object_default_value() {
    #[derive(InputObject)]
    struct MyInput {
        #[graphql(default = 999)]
        a: i32,

        #[graphql(default_with = "vec![1, 2, 3]")]
        b: Vec<i32>,

        #[graphql(default = "abc")]
        c: String,

        #[graphql(default = 999)]
        d: i32,

        #[graphql(default = 999)]
        e: i32,
    }

    struct MyOutput {
        a: i32,
        b: Vec<i32>,
        c: String,
        d: Option<i32>,
        e: Option<i32>,
    }

    #[Object]
    impl MyOutput {
        async fn a(&self) -> i32 {
            self.a
        }

        async fn b(&self) -> &Vec<i32> {
            &self.b
        }

        async fn c(&self) -> &String {
            &self.c
        }

        async fn d(&self) -> &Option<i32> {
            &self.d
        }

        async fn e(&self) -> &Option<i32> {
            &self.e
        }
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput) -> MyOutput {
            MyOutput {
                a: input.a,
                b: input.b,
                c: input.c,
                d: Some(input.d),
                e: Some(input.e),
            }
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{e:777}) {
                a b c d e
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": 999,
                "b": [1, 2, 3],
                "c": "abc",
                "d": 999,
                "e": 777,
            }
        })
    );
}

#[tokio::test]
pub async fn test_inputobject_derive_and_item_attributes() {
    use serde::Deserialize;

    #[derive(Deserialize, PartialEq, Debug, InputObject)]
    struct MyInputObject {
        #[serde(alias = "other")]
        real: i32,
    }

    assert_eq!(
        serde_json::from_str::<MyInputObject>(r#"{ "other" : 100 }"#).unwrap(),
        MyInputObject { real: 100 }
    );
}

#[tokio::test]
pub async fn test_inputobject_flatten_recursive() {
    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct A {
        a: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct B {
        #[graphql(default = 70)]
        b: i32,
        #[graphql(flatten)]
        a_obj: A,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct MyInputObject {
        #[graphql(flatten)]
        b_obj: B,
        c: i32,
    }

    assert_eq!(
        MyInputObject::parse(Some(value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })))
        .unwrap(),
        MyInputObject {
            b_obj: B {
                b: 20,
                a_obj: A { a: 10 }
            },
            c: 30,
        }
    );

    assert_eq!(
        MyInputObject {
            b_obj: B {
                b: 20,
                a_obj: A { a: 10 }
            },
            c: 30,
        }
        .to_value(),
        value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })
    );

    struct Query;

    #[Object]
    impl Query {
        async fn test(&self, input: MyInputObject) -> i32 {
            input.c + input.b_obj.b + input.b_obj.a_obj.a
        }

        async fn test_with_default(
            &self,
            #[graphql(default_with = r#"MyInputObject {
            b_obj: B {
                b: 2,
                a_obj: A { a: 1 }
            },
            c: 3,
        }"#)]
            input: MyInputObject,
        ) -> i32 {
            input.c + input.b_obj.b + input.b_obj.a_obj.a
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, b: 20, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 60,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 110,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            testWithDefault
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "testWithDefault": 6,
        })
    );
}

#[tokio::test]
pub async fn test_inputobject_flatten_multiple() {
    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct A {
        a: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct B {
        b: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct C {
        c: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct Abc {
        #[graphql(flatten)]
        a: A,

        #[graphql(flatten)]
        b: B,

        #[graphql(flatten)]
        c: C,
    }

    assert_eq!(
        Abc::parse(Some(value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })))
        .unwrap(),
        Abc {
            a: A { a: 10 },
            b: B { b: 20 },
            c: C { c: 30 }
        }
    );

    assert_eq!(
        Abc {
            a: A { a: 10 },
            b: B { b: 20 },
            c: C { c: 30 }
        }
        .to_value(),
        value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })
    );
}

#[tokio::test]
pub async fn test_input_object_skip_field() {
    #[derive(InputObject)]
    struct MyInput2 {
        a: i32,
        #[graphql(skip)]
        b: i32,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput2) -> i32 {
            assert_eq!(input.b, i32::default());
            input.a
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{a: 777})
        }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "a": 777
        })
    );
}

#[tokio::test]
pub async fn test_box_input_object() {
    #[derive(InputObject)]
    struct MyInput {
        value: i32,
        input: Option<Box<MyInput>>,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn q(&self, input: MyInput) -> i32 {
            input.value
                + input.input.as_ref().unwrap().value
                + input.input.as_ref().unwrap().input.as_ref().unwrap().value
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            q(input: {value: 100, input: { value: 200, input: { value: 300 } } })
        }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "q": 600
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 1, b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": true,
            }
        })
    );

    assert_eq!(
        schema
            .execute("{ obj(input: {b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 10,
                "b": true,
            }
        })
    );

    assert_eq!(<MyObject as InputType>::type_name(), "MyObjectInput");
    assert_eq!(<MyObject as OutputType>::type_name(), "MyObject");
}

#[tokio::test]
pub async fn test_both_input_output_2() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(name = "MyObj", input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    assert_eq!(<MyObject as InputType>::type_name(), "MyObjectInput");
    assert_eq!(<MyObject as OutputType>::type_name(), "MyObj");
}

#[test]
#[should_panic]
pub fn test_both_input_output_with_same_name() {
    #[derive(SimpleObject, InputObject)]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    Schema::new(Query, EmptyMutation, EmptySubscription);
}

#[tokio::test]
pub async fn test_both_input_output_flatten() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "ABCInput")]
    #[graphql(name = "ABC")]
    #[allow(clippy::upper_case_acronyms)]
    struct ABC {
        a: i32,
        #[graphql(flatten)]
        bc: BC,
    }

    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "BCInput")]
    struct BC {
        b: i32,
        c: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: ABC) -> ABC {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1, b: 2, c: 3 }) { a b c } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": 2,
                "c": 3
            }
        })
    );
}

#[tokio::test]
pub async fn test_skip_input() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
        #[graphql(skip_input)]
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1 }) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": 0,
            }
        })
    );
}

#[tokio::test]
pub async fn test_skip_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
        #[graphql(skip_output)]
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1, b: 2 }) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
            }
        })
    );
}

#[tokio::test]
pub async fn test_complex_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[graphql(complex)]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
    }

    #[ComplexObject]
    impl MyObject {
        async fn double(&self) -> i32 {
            self.a * 2
        }
    }

    struct Query;
    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1 }) { a, double } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "double": 2,
            }
        })
    );
}

#[tokio::test]
pub async fn test_input_object_process_with() {
    mod processor {
        pub fn string(input: &mut String) {
            while let Some(ch) = input.pop() {
                if !ch.is_whitespace() {
                    input.push(ch);
                    break;
                }
            }
        }
    }
    #[derive(InputObject)]
    struct MyInput {
        // processor does nothing on default value
        #[graphql(default = "  ", process_with = "processor::string")]
        a: String,

        #[graphql(process_with = "processor::string")]
        b: String,
    }

    struct MyOutput {
        a: String,
        b: String,
    }

    #[Object]
    impl MyOutput {
        async fn a(&self) -> &String {
            &self.a
        }

        async fn b(&self) -> &String {
            &self.b
        }
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput) -> MyOutput {
            MyOutput {
                a: input.a,
                b: input.b,
            }
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{b: "test b   "}) {
                a b
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": "  ",
                "b": "test b",
            }
        })
    );

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{a: "test a ", b: "test"}) {
                a b
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": "test a",
                "b": "test",
            }
        })
    );
}

#[tokio::test]
pub async fn test_input_object_validator() {
    fn check_my_object(obj: &MyInput) -> Result<(), &'static str> {
        if obj.a < 100 || obj.b < 100 {
            Err("invalid MyInput")
        } else {
            Ok(())
        }
    }

    #[derive(InputObject)]
    #[graphql(validator = "check_my_object")]
    struct MyInput {
        a: i32,
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn a(&self, input: MyInput) -> i32 {
            input.a + input.b
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);

    assert_eq!(
        schema
            .execute("{ a(input: { a: 200, b: 300 }) }")
            .await
            .data,
        value!({ "a": 500 })
    );

    assert_eq!(
        schema
            .execute("{ a(input: { a: 100, b: 25 }) }")
            .await
            .into_result()
            .unwrap_err(),
        vec![ServerError {
            message: r#"Failed to parse "MyInput": invalid MyInput"#.to_string(),
            source: None,
            locations: vec![Pos {
                line: 1,
                column: 12
            }],
            path: vec![PathSegment::Field("a".to_string())],
            extensions: None
        }]
    );
}