apollo_compiler/introspection/
mod.rs

1//! Partial execition of the
2//! [schema introspection](https://spec.graphql.org/draft/#sec-Schema-Introspection)
3//! portion of a query
4//!
5//! The main entry point is [`partial_execute`].
6
7use crate::collections::HashMap;
8use crate::executable::Operation;
9#[cfg(doc)]
10use crate::executable::OperationMap;
11#[cfg(doc)]
12use crate::request::coerce_variable_values;
13use crate::request::RequestError;
14use crate::resolvers::Execution;
15use crate::resolvers::FieldError;
16use crate::resolvers::ObjectValue;
17use crate::resolvers::ResolveInfo;
18use crate::resolvers::ResolvedValue;
19use crate::response::ExecutionResponse;
20use crate::response::JsonMap;
21use crate::schema::Implementers;
22use crate::validation::Valid;
23use crate::ExecutableDocument;
24use crate::Name;
25use crate::Schema;
26
27mod max_depth;
28pub(crate) mod resolvers;
29
30/// Check that the nesting level of some list fields does not exceed a fixed depth limit.
31///
32/// Since [the schema-introspection schema][s] is recursive,
33/// a malicious query could cause huge responses that grow exponentially to the nesting depth.
34///
35/// An error result is a [request error](https://spec.graphql.org/draft/#request-error):
36/// execution must not run at all,
37/// and the GraphQL response must not have a `data` key (which is different from `data: null`).
38///
39/// The exact criteria may change in future apollo-compiler versions.
40///
41/// [s]: https://spec.graphql.org/draft/#sec-Schema-Introspection.Schema-Introspection-Schema
42pub fn check_max_depth(
43    document: &Valid<ExecutableDocument>,
44    operation: &Operation,
45) -> Result<(), RequestError> {
46    let initial_depth = 0;
47    max_depth::check_selection_set(
48        document,
49        &mut HashMap::default(),
50        initial_depth,
51        &operation.selection_set,
52    )
53    .map(drop)
54}
55
56/// Excecutes the [schema introspection](https://spec.graphql.org/draft/#sec-Schema-Introspection)
57/// portion of a query and returns a partial response.
58///
59/// * Consider calling [`check_max_depth`] before this function
60/// * `implementers_map` is expected to be form
61///   [`schema.implementers_map()`][Schema::implementers_map],
62///   allowing it to be computed once and reused for many queries
63/// * `operation` is expected to be from
64///   [`document.operations.get(operation_name)?`][OperationMap::get]
65/// * `operation` is expected to be a query,
66///   check [`operation.operation_type.is_query()`][Operation::operation_type]
67/// * `variable_values` is expected to be from [`coerce_variable_values`]
68///
69/// Concrete [root fields][Operation::root_fields] (those with an explicit definition in the schema)
70/// are **_silently ignored_**.
71///
72/// Only introspection meta-fields are executed:
73/// `__typename` (at the response root), `__type`, and `__schema`.
74/// If the operation also contains concrete fields,
75/// the caller can execute them separately and merge the two partial responses.
76/// To categorize which kinds of root fields are present, consider using code like:
77///
78/// ```
79/// # use apollo_compiler::executable as exe;
80/// # fn categorize_fields(document: &exe::ExecutableDocument, operation: &exe::Operation) {
81/// let mut has_schema_introspection_fields = false;
82/// let mut has_root_typename_fields = false;
83/// let mut has_concrete_fields = false;
84/// for root_field in operation.root_fields(document) {
85///     match root_field.name.as_str() {
86///         "__type" | "__schema" => has_schema_introspection_fields = true,
87///         "__typename" => has_root_typename_fields = true,
88///         _ => has_concrete_fields = true,
89///     }
90/// }
91/// # }
92/// ```
93pub fn partial_execute(
94    schema: &Valid<Schema>,
95    implementers_map: &HashMap<Name, Implementers>,
96    document: &Valid<ExecutableDocument>,
97    operation: &Operation,
98    variable_values: &Valid<JsonMap>,
99) -> Result<ExecutionResponse, RequestError> {
100    struct InitialValue<'a> {
101        type_name: &'a str,
102    }
103
104    impl ObjectValue for InitialValue<'_> {
105        fn type_name(&self) -> &str {
106            self.type_name
107        }
108
109        fn resolve_field<'a>(
110            &'a self,
111            _info: &'a ResolveInfo<'a>,
112        ) -> Result<ResolvedValue<'a>, FieldError> {
113            // Introspection meta-fields are handled separately
114            // so this is only called for concrete fields of the root query type
115            Ok(ResolvedValue::SkipForPartialExecution)
116        }
117    }
118
119    let initial_value = InitialValue {
120        type_name: operation.object_type(),
121    };
122    Execution::new(schema, document)
123        .implementers_map(implementers_map)
124        .operation(operation)
125        .coerced_variable_values(variable_values)
126        .enable_schema_introspection(true)
127        .execute_sync(&initial_value)
128}