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,
10 utils::{ValidationError, ValidationErrorContext},
11};
12
13use crate::{
14 ast::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 validation_plan
79 .rules
80 .iter()
81 .for_each(|rule| rule.validate(&mut validation_context, &mut error_collector));
82
83 error_collector.errors
84}
85
86#[test]
87fn cyclic_fragment_should_never_loop() {
88 use crate::validation::rules::default_rules_validation_plan;
89 use crate::validation::test_utils::*;
90
91 let mut default_plan = default_rules_validation_plan();
92 let errors = test_operation_with_schema(
93 "
94 {
95 dog {
96 nickname
97 ...bark
98 ...parents
99 }
100 }
101
102 fragment bark on Dog {
103 barkVolume
104 ...parents
105 }
106
107 fragment parents on Dog {
108 mother {
109 ...bark
110 }
111 }
112
113 ",
114 TEST_SCHEMA,
115 &mut default_plan,
116 );
117
118 let messages = get_messages(&errors);
119 assert_eq!(errors[0].error_code, "NoFragmentsCycle");
120 assert_eq!(messages.len(), 1);
121 assert_eq!(
122 messages,
123 vec!["Cannot spread fragment \"bark\" within itself via \"parents\"."]
124 )
125}
126
127#[test]
128fn simple_self_reference_fragment_should_not_loop() {
129 use crate::validation::rules::default_rules_validation_plan;
130 use crate::validation::test_utils::*;
131
132 let mut default_plan = default_rules_validation_plan();
133 let errors = test_operation_with_schema(
134 "
135 query dog {
136 dog {
137 ...DogFields
138 }
139 }
140
141 fragment DogFields on Dog {
142 mother {
143 ...DogFields
144 }
145 father {
146 ...DogFields
147 }
148 }
149 ",
150 TEST_SCHEMA,
151 &mut default_plan,
152 );
153
154 let messages = get_messages(&errors);
155 assert_eq!(messages.len(), 2);
156 assert_eq!(
157 messages,
158 vec![
159 "Cannot spread fragment \"DogFields\" within itself.",
160 "Cannot spread fragment \"DogFields\" within itself."
161 ]
162 )
163}
164
165#[test]
166fn fragment_loop_through_multiple_frags() {
167 use crate::validation::rules::default_rules_validation_plan;
168 use crate::validation::test_utils::*;
169
170 let mut default_plan = default_rules_validation_plan();
171 let errors = test_operation_with_schema(
172 "
173 query dog {
174 dog {
175 ...DogFields1
176 }
177 }
178
179 fragment DogFields1 on Dog {
180 barks
181 ...DogFields2
182 }
183
184 fragment DogFields2 on Dog {
185 barkVolume
186 ...DogFields3
187 }
188
189 fragment DogFields3 on Dog {
190 name
191 ...DogFields1
192 }
193 ",
194 TEST_SCHEMA,
195 &mut default_plan,
196 );
197
198 let messages = get_messages(&errors);
199 assert_eq!(messages.len(), 1);
200 assert_eq!(
201 messages,
202 vec![
203 "Cannot spread fragment \"DogFields1\" within itself via \"DogFields2\", \"DogFields3\"."
204 ]
205 )
206}