Skip to content

Commit 3f6093b

Browse files
committed
protect and test against cyclic loops
bump 0.0.13
1 parent 28d26fc commit 3f6093b

File tree

4 files changed

+101
-16
lines changed

4 files changed

+101
-16
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "graphql-tools"
3-
version = "0.0.12"
3+
version = "0.0.13"
44
edition = "2021"
55
description = "Tools for working with GraphQL in Rust, based on graphql-parser Document."
66
license = "MIT/Apache-2.0"

src/validation/rules/overlapping_fields_can_be_merged.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::ValidationRule;
22
use crate::static_graphql::query::*;
33
use crate::validation::utils::{ValidationError, ValidationErrorContext};
44
use crate::{ast::QueryVisitor, validation::utils::ValidationContext};
5-
use std::collections::HashMap;
5+
use std::collections::{HashMap, HashSet};
66

77
/// Overlapping fields can be merged
88
///
@@ -17,6 +17,7 @@ struct FindOverlappingFieldsThatCanBeMergedHelper<'a> {
1717
discoverd_fields: HashMap<String, Field>,
1818
validation_context: &'a ValidationErrorContext<'a>,
1919
selection_set_errors: Vec<ValidationError>,
20+
visited_fragments: HashSet<String>,
2021
}
2122

2223
impl<'a> FindOverlappingFieldsThatCanBeMergedHelper<'a> {
@@ -25,6 +26,7 @@ impl<'a> FindOverlappingFieldsThatCanBeMergedHelper<'a> {
2526
discoverd_fields: HashMap::new(),
2627
selection_set_errors: Vec::new(),
2728
validation_context: ctx,
29+
visited_fragments: HashSet::new(),
2830
}
2931
}
3032

@@ -101,6 +103,9 @@ impl<'a> FindOverlappingFieldsThatCanBeMergedHelper<'a> {
101103
}
102104

103105
Selection::FragmentSpread(fragment_spread) => {
106+
if !self.visited_fragments.contains(&fragment_spread.fragment_name) {
107+
self.visited_fragments.insert(fragment_spread.fragment_name.clone());
108+
104109
if let Some(fragment) = self
105110
.validation_context
106111
.ctx
@@ -116,6 +121,7 @@ impl<'a> FindOverlappingFieldsThatCanBeMergedHelper<'a> {
116121
}
117122
}
118123
}
124+
}
119125
}
120126
}
121127
}

src/validation/validate.rs

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,36 +53,115 @@ pub fn validate<'a>(
5353
}
5454

5555
#[test]
56-
fn test_validate_valid_query() {
56+
fn cyclic_fragment_should_never_loop() {
5757
use crate::validation::test_utils::*;
58+
use crate::validation::rules::default_rules_validation_plan;
5859

59-
let mut default_plan = create_default_ruleset_plan();
60-
let errors = test_operation_without_schema(
60+
let mut default_plan = default_rules_validation_plan();
61+
let errors = test_operation_with_schema(
6162
"
62-
query test {
63-
foo
64-
}
63+
{
64+
dog {
65+
nickname
66+
...bark
67+
...parents
68+
}
69+
}
70+
71+
fragment bark on Dog {
72+
barkVolume
73+
...parents
74+
}
75+
76+
fragment parents on Dog {
77+
mother {
78+
...bark
79+
}
80+
}
81+
6582
",
83+
TEST_SCHEMA,
6684
&mut default_plan,
6785
);
6886

69-
assert_eq!(errors.len(), 0);
87+
let messages = get_messages(&errors);
88+
assert_eq!(messages.len(), 1);
89+
assert_eq!(messages, vec![
90+
"Cannot spread fragment \"bark\" within itself via \"parents\"."
91+
])
7092
}
7193

7294
#[test]
73-
fn test_validate_valid_fragment() {
95+
fn simple_self_reference_fragment_should_not_loop() {
7496
use crate::validation::test_utils::*;
97+
use crate::validation::rules::default_rules_validation_plan;
7598

76-
let mut default_plan = create_default_ruleset_plan();
77-
let errors = test_operation_without_schema(
99+
let mut default_plan = default_rules_validation_plan();
100+
let errors = test_operation_with_schema(
78101
"
79-
fragment uniqueFields on Dog {
102+
query dog {
103+
dog {
104+
...DogFields
105+
}
106+
}
107+
108+
fragment DogFields on Dog {
109+
mother {
110+
...DogFields
111+
}
112+
father {
113+
...DogFields
114+
}
115+
}
116+
",
117+
TEST_SCHEMA,
118+
&mut default_plan,
119+
);
120+
121+
let messages = get_messages(&errors);
122+
assert_eq!(messages.len(), 2);
123+
assert_eq!(messages, vec![
124+
"Cannot spread fragment \"DogFields\" within itself.",
125+
"Cannot spread fragment \"DogFields\" within itself."
126+
])
127+
}
128+
129+
#[test]
130+
fn fragment_loop_through_multiple_frags() {
131+
use crate::validation::test_utils::*;
132+
use crate::validation::rules::default_rules_validation_plan;
133+
134+
let mut default_plan = default_rules_validation_plan();
135+
let errors = test_operation_with_schema(
136+
"
137+
query dog {
138+
dog {
139+
...DogFields1
140+
}
141+
}
142+
143+
fragment DogFields1 on Dog {
144+
barks
145+
...DogFields2
146+
}
147+
148+
fragment DogFields2 on Dog {
149+
barkVolume
150+
...DogFields3
151+
}
152+
153+
fragment DogFields3 on Dog {
80154
name
81-
nickname
155+
...DogFields1
82156
}
83157
",
158+
TEST_SCHEMA,
84159
&mut default_plan,
85160
);
86161

87-
assert_eq!(errors.len(), 0);
162+
let messages = get_messages(&errors);
163+
assert_eq!(messages.len(), 1);
164+
assert_eq!(messages, vec![
165+
"Cannot spread fragment \"DogFields1\" within itself via \"DogFields2\", \"DogFields3\"."
166+
])
88167
}

0 commit comments

Comments
 (0)