apollo_compiler/introspection/
mod.rs

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
//! [Execution](https://spec.graphql.org/draft/#sec-Execution) engine
//! for the [schema introspection](https://spec.graphql.org/draft/#sec-Schema-Introspection)
//! portion of a query
//!
//! The main entry point is [`partial_execute`].

use crate::collections::HashMap;
use crate::executable::Operation;
#[cfg(doc)]
use crate::executable::OperationMap;
use crate::execution::engine::execute_selection_set;
use crate::execution::engine::ExecutionMode;
use crate::execution::engine::PropagateNull;
#[cfg(doc)]
use crate::request::coerce_variable_values;
use crate::request::RequestError;
use crate::response::ExecutionResponse;
use crate::response::JsonMap;
use crate::schema::Implementers;
use crate::validation::Valid;
use crate::ExecutableDocument;
use crate::Name;
use crate::Schema;

mod max_depth;
mod resolvers;

/// Check that the nesting level of some list fields does not exceed a fixed depth limit.
///
/// Since [the schema-introspection schema][s] is recursive,
/// a malicious query could cause huge responses that grow exponentially to the nesting depth.
///
/// An error result is a [request error](https://spec.graphql.org/draft/#request-error):
/// execution must not run at all,
/// and the GraphQL response must not have a `data` key (which is different from `data: null`).
///
/// The exact criteria may change in future apollo-compiler versions.
///
/// [s]: https://spec.graphql.org/draft/#sec-Schema-Introspection.Schema-Introspection-Schema
pub fn check_max_depth(
    document: &Valid<ExecutableDocument>,
    operation: &Operation,
) -> Result<(), RequestError> {
    let initial_depth = 0;
    max_depth::check_selection_set(document, initial_depth, &operation.selection_set)
}

/// Excecutes the [schema introspection](https://spec.graphql.org/draft/#sec-Schema-Introspection)
/// portion of a query and returns a partial response.
///
/// * Consider calling [`check_max_depth`] before this function
/// * `implementers_map` is expected to be form
///   [`schema.implementers_map()`][Schema::implementers_map],
///   allowing it to be computed once and reused for many queries
/// * `operation` is expected to be from
///   [`document.operations.get(operation_name)?`][OperationMap::get]
/// * `operation` is expected to be a query,
///   check [`operation.operation_type.is_query()`][Operation::operation_type]
/// * `variable_values` is expected to be from [`coerce_variable_values`]
///
/// Concrete [root fields][Operation::root_fields] (those with an explicit definition in the schema)
/// are **_silently ignored_**.
///
/// Only introspection meta-fields are executed:
/// `__typename` (at the response root), `__type`, and `__schema`.
/// If the operation also contains concrete fields,
/// the caller can execute them separately and merge the two partial responses.
/// To categorize which kinds of root fields are present, consider using code like:
///
/// ```
/// # use apollo_compiler::executable as exe;
/// # fn categorize_fields(document: &exe::ExecutableDocument, operation: &exe::Operation) {
/// let mut has_schema_introspection_fields = false;
/// let mut has_root_typename_fields = false;
/// let mut has_concrete_fields = false;
/// for root_field in operation.root_fields(document) {
///     match root_field.name.as_str() {
///         "__type" | "__schema" => has_schema_introspection_fields = true,
///         "__typename" => has_root_typename_fields = true,
///         _ => has_concrete_fields = true,
///     }
/// }
/// # }
/// ```
pub fn partial_execute(
    schema: &Valid<Schema>,
    implementers_map: &HashMap<Name, Implementers>,
    document: &Valid<ExecutableDocument>,
    operation: &Operation,
    variable_values: &Valid<JsonMap>,
) -> Result<ExecutionResponse, RequestError> {
    let object_type_name = operation.object_type();
    let Some(root_operation_object_type_def) = schema.get_object(object_type_name) else {
        return Err(RequestError {
            message: "Undefined root operation type".to_owned(),
            location: object_type_name.location(),
            is_suspected_validation_bug: true,
        });
    };

    let initial_value =
        &resolvers::IntrospectionRootResolver(resolvers::SchemaWithImplementersMap {
            schema,
            implementers_map,
        });
    let mut errors = Vec::new();
    let path = None;
    let data = match execute_selection_set(
        schema,
        document,
        variable_values,
        &mut errors,
        path,
        ExecutionMode::Normal,
        root_operation_object_type_def,
        initial_value,
        &operation.selection_set.selections,
    ) {
        Ok(map) => Some(map),
        Err(PropagateNull) => None,
    };
    Ok(ExecutionResponse { data, errors })
}