async_graphql/extensions/
analyzer.rs

1use std::sync::Arc;
2
3use futures_util::lock::Mutex;
4
5use crate::{
6    extensions::{Extension, ExtensionContext, ExtensionFactory, NextRequest, NextValidation},
7    value, Response, ServerError, ValidationResult,
8};
9
10/// Analyzer extension
11///
12/// This extension will output the `analyzer` field containing `complexity` and
13/// `depth` in the response extension of each query.
14pub struct Analyzer;
15
16impl ExtensionFactory for Analyzer {
17    fn create(&self) -> Arc<dyn Extension> {
18        Arc::new(AnalyzerExtension::default())
19    }
20}
21
22#[derive(Default)]
23struct AnalyzerExtension {
24    validation_result: Mutex<Option<ValidationResult>>,
25}
26
27#[async_trait::async_trait]
28impl Extension for AnalyzerExtension {
29    async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
30        let mut resp = next.run(ctx).await;
31        let validation_result = self.validation_result.lock().await.take();
32        if let Some(validation_result) = validation_result {
33            resp = resp.extension(
34                "analyzer",
35                value! ({
36                    "complexity": validation_result.complexity,
37                    "depth": validation_result.depth,
38                }),
39            );
40        }
41        resp
42    }
43
44    async fn validation(
45        &self,
46        ctx: &ExtensionContext<'_>,
47        next: NextValidation<'_>,
48    ) -> Result<ValidationResult, Vec<ServerError>> {
49        let res = next.run(ctx).await?;
50        *self.validation_result.lock().await = Some(res);
51        Ok(res)
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use crate::*;
58
59    struct Query;
60
61    #[derive(Copy, Clone)]
62    struct MyObj;
63
64    #[Object(internal)]
65    impl MyObj {
66        async fn value(&self) -> i32 {
67            1
68        }
69
70        async fn obj(&self) -> MyObj {
71            MyObj
72        }
73    }
74
75    #[Object(internal)]
76    impl Query {
77        async fn value(&self) -> i32 {
78            1
79        }
80
81        async fn obj(&self) -> MyObj {
82            MyObj
83        }
84
85        #[graphql(complexity = "count * child_complexity")]
86        async fn objs(&self, count: usize) -> Vec<MyObj> {
87            vec![MyObj; count]
88        }
89    }
90
91    #[tokio::test]
92    async fn analyzer() {
93        let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
94            .extension(extensions::Analyzer)
95            .finish();
96
97        let res = schema
98            .execute(
99                r#"{
100            value obj {
101                value obj {
102                    value
103                }
104            }
105            objs(count: 10) { value }
106        }"#,
107            )
108            .await
109            .into_result()
110            .unwrap()
111            .extensions
112            .remove("analyzer");
113        assert_eq!(
114            res,
115            Some(value!({
116                "complexity": 5 + 10,
117                "depth": 3,
118            }))
119        );
120    }
121}