graphql_tools/validation/
validate.rs1use std::{
2 hash::{Hash as _, Hasher as _},
3 sync::Arc,
4};
5
6use xxhash_rust::xxh3::Xxh3;
7
8use super::{
9 rules::{ValidationRule, ValidationVisitors},
10 utils::{ValidationError, ValidationErrorContext},
11};
12
13use crate::{
14 ast::{visit_document, OperationVisitorContext},
15 static_graphql::{query, schema},
16};
17
18#[derive(Clone)]
19pub struct ValidationPlan {
20 pub rules: Vec<Arc<Box<dyn ValidationRule>>>,
21 pub hash: u64,
22}
23
24#[inline]
25fn calculate_hash(rules: &[Arc<Box<dyn ValidationRule>>]) -> u64 {
26 let mut hasher = Xxh3::new();
27 for rule in rules {
28 rule.error_code().hash(&mut hasher);
29 }
30 hasher.finish()
31}
32
33impl ValidationPlan {
34 pub fn new() -> Self {
35 let rules = vec![];
36 Self {
37 hash: calculate_hash(&rules),
38 rules,
39 }
40 }
41
42 pub fn from(rules: Vec<Box<dyn ValidationRule>>) -> Self {
43 let rules = rules
44 .into_iter()
45 .map(|rule| rule.into())
46 .collect::<Vec<Arc<Box<dyn ValidationRule>>>>();
47
48 Self {
49 hash: calculate_hash(&rules),
50 rules,
51 }
52 }
53
54 pub fn add_rule(&mut self, rule: Box<dyn ValidationRule>) {
55 self.rules.push(Arc::new(rule));
56 self.recalculate_hash();
57 }
58
59 fn recalculate_hash(&mut self) {
60 self.hash = calculate_hash(&self.rules);
61 }
62}
63
64impl Default for ValidationPlan {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70pub fn validate<'a>(
71 schema: &'a schema::Document,
72 operation: &'a query::Document,
73 validation_plan: &'a ValidationPlan,
74) -> Vec<ValidationError> {
75 let mut error_collector = ValidationErrorContext::new();
76 let mut validation_context = OperationVisitorContext::new(operation, schema);
77
78 let mut visitor_instances = Vec::with_capacity(validation_plan.rules.len());
79 for rule in &validation_plan.rules {
80 visitor_instances.push(rule.visitor());
81 }
82 let mut visitors = ValidationVisitors::new(visitor_instances);
83
84 if !visitors.is_empty() {
85 visit_document(
86 &mut visitors,
87 operation,
88 &mut validation_context,
89 &mut error_collector,
90 );
91 }
92
93 error_collector.errors
94}
95
96#[test]
97fn cyclic_fragment_should_never_loop() {
98 use crate::validation::rules::default_rules_validation_plan;
99 use crate::validation::test_utils::*;
100
101 let mut default_plan = default_rules_validation_plan();
102 let errors = test_operation_with_schema(
103 "
104 {
105 dog {
106 nickname
107 ...bark
108 ...parents
109 }
110 }
111
112 fragment bark on Dog {
113 barkVolume
114 ...parents
115 }
116
117 fragment parents on Dog {
118 mother {
119 ...bark
120 }
121 }
122
123 ",
124 TEST_SCHEMA,
125 &mut default_plan,
126 );
127
128 let messages = get_messages(&errors);
129 assert_eq!(errors[0].error_code, "NoFragmentsCycle");
130 assert_eq!(messages.len(), 1);
131 assert_eq!(
132 messages,
133 vec!["Cannot spread fragment \"bark\" within itself via \"parents\"."]
134 )
135}
136
137#[test]
138fn simple_self_reference_fragment_should_not_loop() {
139 use crate::validation::rules::default_rules_validation_plan;
140 use crate::validation::test_utils::*;
141
142 let mut default_plan = default_rules_validation_plan();
143 let errors = test_operation_with_schema(
144 "
145 query dog {
146 dog {
147 ...DogFields
148 }
149 }
150
151 fragment DogFields on Dog {
152 mother {
153 ...DogFields
154 }
155 father {
156 ...DogFields
157 }
158 }
159 ",
160 TEST_SCHEMA,
161 &mut default_plan,
162 );
163
164 let messages = get_messages(&errors);
165 assert_eq!(messages.len(), 2);
166 assert_eq!(
167 messages,
168 vec![
169 "Cannot spread fragment \"DogFields\" within itself.",
170 "Cannot spread fragment \"DogFields\" within itself."
171 ]
172 )
173}
174
175#[test]
176fn fragment_loop_through_multiple_frags() {
177 use crate::validation::rules::default_rules_validation_plan;
178 use crate::validation::test_utils::*;
179
180 let mut default_plan = default_rules_validation_plan();
181 let errors = test_operation_with_schema(
182 "
183 query dog {
184 dog {
185 ...DogFields1
186 }
187 }
188
189 fragment DogFields1 on Dog {
190 barks
191 ...DogFields2
192 }
193
194 fragment DogFields2 on Dog {
195 barkVolume
196 ...DogFields3
197 }
198
199 fragment DogFields3 on Dog {
200 name
201 ...DogFields1
202 }
203 ",
204 TEST_SCHEMA,
205 &mut default_plan,
206 );
207
208 let messages = get_messages(&errors);
209 assert_eq!(messages.len(), 1);
210 assert_eq!(
211 messages,
212 vec![
213 "Cannot spread fragment \"DogFields1\" within itself via \"DogFields2\", \"DogFields3\"."
214 ]
215 )
216}