Skip to main content

graphql_tools/validation/
validate.rs

1use 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}