1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#[cfg(test)]
use std::collections::HashMap;
use std::num::NonZeroUsize;
use std::sync::Arc;

use router_bridge::introspect::IntrospectionError;
use router_bridge::planner::Planner;

use crate::cache::storage::CacheStorage;
use crate::graphql::Response;
use crate::query_planner::QueryPlanResult;

const DEFAULT_INTROSPECTION_CACHE_CAPACITY: NonZeroUsize =
    unsafe { NonZeroUsize::new_unchecked(5) };

/// A cache containing our well known introspection queries.
pub(crate) struct Introspection {
    cache: CacheStorage<String, Response>,
    planner: Arc<Planner<QueryPlanResult>>,
}

impl Introspection {
    pub(crate) async fn with_capacity(
        planner: Arc<Planner<QueryPlanResult>>,
        capacity: NonZeroUsize,
    ) -> Self {
        Self {
            cache: CacheStorage::new(capacity, None, "introspection").await,
            planner,
        }
    }

    pub(crate) async fn new(planner: Arc<Planner<QueryPlanResult>>) -> Self {
        Self::with_capacity(planner, DEFAULT_INTROSPECTION_CACHE_CAPACITY).await
    }

    #[cfg(test)]
    pub(crate) async fn from_cache(
        planner: Arc<Planner<QueryPlanResult>>,
        cache: HashMap<String, Response>,
    ) -> Self {
        let this = Self::with_capacity(planner, cache.len().try_into().unwrap()).await;

        for (query, response) in cache.into_iter() {
            this.cache.insert(query, response).await;
        }
        this
    }

    /// Execute an introspection and cache the response.
    pub(crate) async fn execute(&self, query: String) -> Result<Response, IntrospectionError> {
        if let Some(response) = self.cache.get(&query).await {
            return Ok(response);
        }

        // Do the introspection query and cache it
        let response =
            self.planner
                .introspect(query.clone())
                .await
                .map_err(|_e| IntrospectionError {
                    message: String::from("cannot find the introspection response").into(),
                })?;

        let introspection_result = response.into_result().map_err(|err| IntrospectionError {
            message: format!(
                "introspection error : {}",
                err.into_iter()
                    .map(|err| err.to_string())
                    .collect::<Vec<String>>()
                    .join(", "),
            )
            .into(),
        })?;

        let response = Response::builder().data(introspection_result).build();

        self.cache.insert(query, response.clone()).await;

        Ok(response)
    }
}

#[cfg(test)]
mod introspection_tests {
    use std::sync::Arc;

    use router_bridge::planner::IncrementalDeliverySupport;
    use router_bridge::planner::QueryPlannerConfig;

    use super::*;

    #[tokio::test]
    async fn test_plan_cache() {
        let query_to_test = r#"{
            __schema {
              types {
                name
              }
            }
          }"#;
        let schema = include_str!("../tests/fixtures/supergraph.graphql");
        let expected_data = Response::builder().data(42).build();

        let planner = Arc::new(
            Planner::new(
                schema.to_string(),
                QueryPlannerConfig {
                    incremental_delivery: Some(IncrementalDeliverySupport {
                        enable_defer: Some(true),
                    }),
                },
            )
            .await
            .unwrap(),
        );

        let cache = [(query_to_test.to_string(), expected_data.clone())]
            .iter()
            .cloned()
            .collect();
        let introspection = Introspection::from_cache(planner, cache).await;

        assert_eq!(
            expected_data,
            introspection
                .execute(query_to_test.to_string())
                .await
                .unwrap()
        );
    }
}