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, 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}