Skip to main content

graphql_tools/validation/rules/
no_unused_fragments.rs

1use super::ValidationRule;
2use crate::ast::{visit_document, OperationVisitor, OperationVisitorContext};
3use crate::static_graphql::query::*;
4use crate::validation::utils::{ValidationError, ValidationErrorContext};
5
6/// No unused fragments
7///
8/// A GraphQL document is only valid if all fragment definitions are spread
9/// within operations, or spread within other fragments spread within operations.
10///
11/// See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used
12pub struct NoUnusedFragments<'a> {
13    fragments_in_use: Vec<&'a str>,
14}
15
16impl<'a> OperationVisitor<'a, ValidationErrorContext> for NoUnusedFragments<'a> {
17    fn enter_fragment_spread(
18        &mut self,
19        _: &mut OperationVisitorContext,
20        _: &mut ValidationErrorContext,
21        fragment_spread: &'a FragmentSpread,
22    ) {
23        self.fragments_in_use
24            .push(fragment_spread.fragment_name.as_str());
25    }
26
27    fn leave_document(
28        &mut self,
29        visitor_context: &mut OperationVisitorContext,
30        user_context: &mut ValidationErrorContext,
31        _document: &Document,
32    ) {
33        visitor_context
34            .known_fragments
35            .keys()
36            .filter(|fragment_name| !self.fragments_in_use.contains(fragment_name))
37            .for_each(|unused_fragment_name| {
38                user_context.report_error(ValidationError {
39                    error_code: self.error_code(),
40                    locations: vec![],
41                    message: format!("Fragment \"{}\" is never used.", unused_fragment_name),
42                });
43            });
44    }
45}
46
47impl<'a> Default for NoUnusedFragments<'a> {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl<'a> NoUnusedFragments<'a> {
54    pub fn new() -> Self {
55        NoUnusedFragments {
56            fragments_in_use: Vec::new(),
57        }
58    }
59}
60
61impl<'n> ValidationRule for NoUnusedFragments<'n> {
62    fn error_code<'a>(&self) -> &'a str {
63        "NoUnusedFragments"
64    }
65
66    fn validate(
67        &self,
68        ctx: &mut OperationVisitorContext,
69        error_collector: &mut ValidationErrorContext,
70    ) {
71        visit_document(
72            &mut NoUnusedFragments::new(),
73            ctx.operation,
74            ctx,
75            error_collector,
76        );
77    }
78}
79
80#[test]
81fn all_fragment_names_are_used() {
82    use crate::validation::test_utils::*;
83
84    let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
85    let errors = test_operation_with_schema(
86        "{
87          human(id: 4) {
88            ...HumanFields1
89            ... on Human {
90              ...HumanFields2
91            }
92          }
93        }
94        fragment HumanFields1 on Human {
95          name
96          ...HumanFields3
97        }
98        fragment HumanFields2 on Human {
99          name
100        }
101        fragment HumanFields3 on Human {
102          name
103        }",
104        TEST_SCHEMA,
105        &mut plan,
106    );
107
108    assert_eq!(get_messages(&errors).len(), 0);
109}
110
111#[test]
112fn all_fragment_names_are_used_by_multiple_operations() {
113    use crate::validation::test_utils::*;
114
115    let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
116    let errors = test_operation_with_schema(
117        "query Foo {
118          human(id: 4) {
119            ...HumanFields1
120          }
121        }
122        query Bar {
123          human(id: 4) {
124            ...HumanFields2
125          }
126        }
127        fragment HumanFields1 on Human {
128          name
129          ...HumanFields3
130        }
131        fragment HumanFields2 on Human {
132          name
133        }
134        fragment HumanFields3 on Human {
135          name
136        }
137  ",
138        TEST_SCHEMA,
139        &mut plan,
140    );
141
142    assert_eq!(get_messages(&errors).len(), 0);
143}
144
145#[test]
146fn contains_unknown_fragments() {
147    use crate::validation::test_utils::*;
148
149    let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
150    let errors = test_operation_with_schema(
151        "query Foo {
152          human(id: 4) {
153            ...HumanFields1
154          }
155        }
156        query Bar {
157          human(id: 4) {
158            ...HumanFields2
159          }
160        }
161        fragment HumanFields1 on Human {
162          name
163          ...HumanFields3
164        }
165        fragment HumanFields2 on Human {
166          name
167        }
168        fragment HumanFields3 on Human {
169          name
170        }
171        fragment Unused1 on Human {
172          name
173        }
174        fragment Unused2 on Human {
175          name
176        }
177  ",
178        TEST_SCHEMA,
179        &mut plan,
180    );
181
182    let messages = get_messages(&errors);
183    assert_eq!(messages.len(), 2);
184}
185
186// TODO: Fix this one :( It's not working
187#[test]
188#[ignore = "Fix this one :( It's not working"]
189fn contains_unknown_fragments_with_ref_cycle() {
190    use crate::validation::test_utils::*;
191
192    let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
193    let errors = test_operation_with_schema(
194        "query Foo {
195          human(id: 4) {
196            ...HumanFields1
197          }
198        }
199        query Bar {
200          human(id: 4) {
201            ...HumanFields2
202          }
203        }
204        fragment HumanFields1 on Human {
205          name
206          ...HumanFields3
207        }
208        fragment HumanFields2 on Human {
209          name
210        }
211        fragment HumanFields3 on Human {
212          name
213        }
214        fragment Unused1 on Human {
215          name
216          ...Unused2
217        }
218        fragment Unused2 on Human {
219          name
220          ...Unused1
221        }
222  ",
223        TEST_SCHEMA,
224        &mut plan,
225    );
226
227    let messages = get_messages(&errors);
228    assert_eq!(messages.len(), 2);
229    assert_eq!(
230        messages,
231        vec![
232            "Fragment \"Unused1\" is never used.",
233            "Fragment \"Unused2\" is never used."
234        ]
235    );
236}
237
238#[test]
239fn contains_unknown_and_undef_fragments() {
240    use crate::validation::test_utils::*;
241
242    let mut plan = create_plan_from_rule(Box::new(NoUnusedFragments::new()));
243    let errors = test_operation_with_schema(
244        "query Foo {
245          human(id: 4) {
246            ...bar
247          }
248        }
249        fragment foo on Human {
250          name
251        }
252  ",
253        TEST_SCHEMA,
254        &mut plan,
255    );
256
257    let messages = get_messages(&errors);
258    assert_eq!(messages.len(), 1);
259    assert_eq!(messages, vec!["Fragment \"foo\" is never used.",]);
260}