cynic_introspection/
detection.rs

1//! Defines types for an introspection query that can tell what a GraphQL server
2//! supports.
3
4use crate::capabilities::CapabilitySet;
5
6use super::query::schema;
7
8#[derive(cynic::QueryFragment, Debug)]
9#[cynic(graphql_type = "Query")]
10/// A query that detects what capabilities a remote GraphQL server has, which can be used
11/// to determine what introspection query should be made against that server.
12///
13/// Currently this just determines which version of the specification the server supports,
14/// but it may be extended in the future to detect other capabilities e.g. which RFCs a
15/// server has implemented support for.
16///
17/// ```rust
18/// use cynic::{QueryBuilder, http::ReqwestExt};
19/// use cynic_introspection::CapabilitiesQuery;
20/// # #[tokio::main]
21/// # async fn main() {
22/// # let server = graphql_mocks::mocks::swapi::serve().await;
23/// # let url = server.url();
24/// # let url = url.as_ref();
25///
26/// let data = reqwest::Client::new()
27///     .post(url)
28///     .run_graphql(CapabilitiesQuery::build(()))
29///     .await
30///     .unwrap()
31///     .data
32///     .unwrap();
33///
34/// let capabilities = data.capabilities();
35/// println!("This server supports {capabilities:?}");
36/// # }
37/// ```
38pub struct CapabilitiesQuery {
39    #[cynic(rename = "__type")]
40    #[arguments(name: "__Type")]
41    type_type: Option<Type>,
42}
43
44impl CapabilitiesQuery {
45    /// The capabilities that were detected by this query
46    pub fn capabilities(&self) -> CapabilitySet {
47        CapabilitySet {
48            specification_version: self.version_supported(),
49        }
50    }
51
52    fn version_supported(&self) -> SpecificationVersion {
53        let Some(type_type) = &self.type_type else {
54            return SpecificationVersion::Unknown;
55        };
56
57        let specified_by_field = type_type
58            .fields
59            .iter()
60            .find(|field| field.name == "specifiedByURL");
61
62        match specified_by_field {
63            Some(_) => SpecificationVersion::October2021,
64            None => SpecificationVersion::June2018,
65        }
66    }
67}
68
69/// Versions of the GraphQL specification that the CapabilitiesQuery can detect.
70#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
71#[non_exhaustive]
72pub enum SpecificationVersion {
73    /// We were unable to determine which version of the GraphQL specification
74    /// this server supports.
75    Unknown,
76    /// The GraphQL specification published in June 2018
77    #[default]
78    June2018,
79    /// The GraphQL specification published in October 2021
80    October2021,
81}
82
83#[derive(cynic::QueryFragment, Debug)]
84#[cynic(graphql_type = "__Type")]
85/// Details about a type in the schema
86struct Type {
87    #[cynic(flatten)]
88    #[arguments(includeDeprecated: true)]
89    fields: Vec<Field>,
90}
91
92#[derive(cynic::QueryFragment, Debug)]
93#[cynic(graphql_type = "__Field")]
94/// Represents one of the fields of an object or interface type
95struct Field {
96    /// The name of the field
97    name: String,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_ordering_of_specification_version() {
106        assert!(SpecificationVersion::June2018 < SpecificationVersion::October2021);
107    }
108}