apollo_compiler/validation/
operation.rs1use crate::collections::HashSet;
2use crate::executable;
3use crate::validation::diagnostics::DiagnosticData;
4use crate::validation::DepthCounter;
5use crate::validation::DepthGuard;
6use crate::validation::DiagnosticList;
7use crate::validation::ExecutableValidationContext;
8use crate::validation::RecursionLimitError;
9use crate::ExecutableDocument;
10use crate::Name;
11use crate::Node;
12
13fn walk_selections<'doc>(
20 document: &'doc ExecutableDocument,
21 selections: &'doc executable::SelectionSet,
22 mut f: impl FnMut(&'doc executable::Selection),
23) -> Result<(), RecursionLimitError> {
24 fn walk_selections_inner<'doc>(
25 document: &'doc ExecutableDocument,
26 selection_set: &'doc executable::SelectionSet,
27 seen: &mut HashSet<&'doc Name>,
28 mut guard: DepthGuard<'_>,
29 f: &mut dyn FnMut(&'doc executable::Selection),
30 ) -> Result<(), RecursionLimitError> {
31 for selection in &selection_set.selections {
32 f(selection);
33 match selection {
34 executable::Selection::Field(_) => {
35 }
37 executable::Selection::FragmentSpread(fragment) => {
38 let new = seen.insert(&fragment.fragment_name);
39 if !new {
40 continue;
41 }
42
43 if let Some(fragment_definition) =
45 document.fragments.get(&fragment.fragment_name)
46 {
47 walk_selections_inner(
48 document,
49 &fragment_definition.selection_set,
50 seen,
51 guard.increment()?,
52 f,
53 )?;
54 }
55 }
56 executable::Selection::InlineFragment(fragment) => {
57 walk_selections_inner(
58 document,
59 &fragment.selection_set,
60 seen,
61 guard.increment()?,
62 f,
63 )?;
64 }
65 }
66 }
67 Ok(())
68 }
69
70 let mut depth = DepthCounter::new().with_limit(500);
75
76 walk_selections_inner(
77 document,
78 selections,
79 &mut HashSet::default(),
80 depth.guard(),
81 &mut f,
82 )
83}
84
85pub(crate) fn validate_subscription(
86 document: &executable::ExecutableDocument,
87 operation: &Node<executable::Operation>,
88 diagnostics: &mut DiagnosticList,
89) {
90 if !operation.is_subscription() {
91 return;
92 }
93
94 let mut field_names = vec![];
95
96 let walked = walk_selections(document, &operation.selection_set, |selection| {
97 if let executable::Selection::Field(field) = selection {
98 field_names.push(field.name.clone());
99 if matches!(field.name.as_str(), "__type" | "__schema" | "__typename") {
100 diagnostics.push(
101 field.location(),
102 executable::BuildError::SubscriptionUsesIntrospection {
103 name: operation.name.clone(),
104 field: field.name.clone(),
105 },
106 );
107 }
108 }
109
110 if let Some(conditional_directive) = selection
111 .directives()
112 .iter()
113 .find(|d| matches!(d.name.as_str(), "skip" | "include"))
114 {
115 diagnostics.push(
116 conditional_directive.location(),
117 executable::BuildError::SubscriptionUsesConditionalSelection {
118 name: operation.name.clone(),
119 },
120 );
121 }
122 });
123
124 if walked.is_err() {
125 diagnostics.push(None, DiagnosticData::RecursionError {});
126 return;
127 }
128
129 if field_names.len() > 1 {
130 diagnostics.push(
131 operation.location(),
132 executable::BuildError::SubscriptionUsesMultipleFields {
133 name: operation.name.clone(),
134 fields: field_names,
135 },
136 );
137 }
138}
139
140pub(crate) fn validate_operation(
141 diagnostics: &mut DiagnosticList,
142 document: &ExecutableDocument,
143 operation: &executable::Operation,
144 context: &ExecutableValidationContext<'_>,
145) {
146 let against_type = if let Some(schema) = context.schema() {
147 schema
148 .root_operation(operation.operation_type)
149 .map(|ty| (schema, ty))
150 } else {
151 None
152 };
153
154 super::directive::validate_directives(
155 diagnostics,
156 context.schema(),
157 operation.directives.iter(),
158 operation.operation_type.into(),
159 &operation.variables,
160 );
161 super::variable::validate_variable_definitions(
162 diagnostics,
163 context.schema(),
164 &operation.variables,
165 );
166
167 super::variable::validate_unused_variables(diagnostics, document, operation);
168 super::selection::validate_selection_set(
169 diagnostics,
170 document,
171 against_type,
172 &operation.selection_set,
173 &mut context.operation_context(&operation.variables),
174 );
175}
176
177pub(crate) fn validate_operation_definitions(
178 diagnostics: &mut DiagnosticList,
179 document: &ExecutableDocument,
180 context: &ExecutableValidationContext<'_>,
181) {
182 for operation in document.operations.iter() {
183 validate_operation(diagnostics, document, operation, context);
184 }
185}