Skip to content

Commit 8d32ea8

Browse files
authored
New validation rule: VariablesInAllowedPosition (#31)
1 parent 3b6ecf7 commit 8d32ea8

12 files changed

+875
-65
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,6 @@ cargo add graphql-tools
6565
- [x] UniqueArgumentNames
6666
- [ ] ValuesOfCorrectType
6767
- [x] ProvidedRequiredArguments
68-
- [ ] VariablesInAllowedPosition
68+
- [x] VariablesInAllowedPosition
6969
- [x] OverlappingFieldsCanBeMerged
7070
- [ ] UniqueInputFieldNames (blocked by https://github.com/graphql-rust/graphql-parser/issues/59)

src/ast/ext.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ pub trait SchemaDocumentExtension {
8686
fn query_type(&self) -> ObjectType;
8787
fn mutation_type(&self) -> Option<ObjectType>;
8888
fn subscription_type(&self) -> Option<ObjectType>;
89+
fn is_subtype(&self, sub_type: &Type, super_type: &Type) -> bool;
90+
fn is_named_subtype(&self, sub_type_name: &String, super_type_name: &String) -> bool;
91+
fn is_possible_type(
92+
&self,
93+
abstract_type: &TypeDefinition,
94+
possible_type: &TypeDefinition,
95+
) -> bool;
8996
}
9097

9198
impl SchemaDocumentExtension for schema::Document {
@@ -170,10 +177,92 @@ impl SchemaDocumentExtension for schema::Document {
170177

171178
type_map
172179
}
180+
181+
fn is_named_subtype(&self, sub_type_name: &String, super_type_name: &String) -> bool {
182+
if sub_type_name == super_type_name {
183+
true
184+
} else if let (Some(sub_type), Some(super_type)) = (
185+
self.type_by_name(sub_type_name),
186+
self.type_by_name(super_type_name),
187+
) {
188+
super_type.is_abstract_type() && self.is_possible_type(&super_type, &sub_type)
189+
} else {
190+
false
191+
}
192+
}
193+
194+
fn is_possible_type(
195+
&self,
196+
abstract_type: &TypeDefinition,
197+
possible_type: &TypeDefinition,
198+
) -> bool {
199+
match abstract_type {
200+
TypeDefinition::Union(union_typedef) => {
201+
return union_typedef.types.contains(&possible_type.name());
202+
}
203+
TypeDefinition::Interface(interface_typedef) => {
204+
let implementes_interfaces = possible_type.interfaces();
205+
206+
return implementes_interfaces.contains(&interface_typedef.name);
207+
}
208+
_ => false,
209+
}
210+
}
211+
212+
fn is_subtype(&self, sub_type: &Type, super_type: &Type) -> bool {
213+
// Equivalent type is a valid subtype
214+
if sub_type == super_type {
215+
return true;
216+
}
217+
218+
// If superType is non-null, maybeSubType must also be non-null.
219+
if super_type.is_non_null() {
220+
if sub_type.is_non_null() {
221+
return self.is_subtype(sub_type.of_type(), super_type.of_type());
222+
}
223+
return false;
224+
}
225+
226+
if sub_type.is_non_null() {
227+
// If superType is nullable, maybeSubType may be non-null or nullable.
228+
return self.is_subtype(sub_type.of_type(), super_type);
229+
}
230+
231+
// If superType type is a list, maybeSubType type must also be a list.
232+
if super_type.is_list_type() {
233+
if sub_type.is_list_type() {
234+
return self.is_subtype(sub_type.of_type(), super_type.of_type());
235+
}
236+
237+
return false;
238+
}
239+
240+
if sub_type.is_list_type() {
241+
// If superType is nullable, maybeSubType may be non-null or nullable.
242+
return false;
243+
}
244+
245+
// If superType type is an abstract type, check if it is super type of maybeSubType.
246+
// Otherwise, the child type is not a valid subtype of the parent type.
247+
if let (Some(sub_type), Some(super_type)) = (
248+
self.type_by_name(&sub_type.inner_type()),
249+
self.type_by_name(&super_type.inner_type()),
250+
) {
251+
return super_type.is_abstract_type()
252+
&& (sub_type.is_interface_type() || sub_type.is_object_type())
253+
&& self.is_possible_type(&super_type, &sub_type);
254+
}
255+
256+
false
257+
}
173258
}
174259

175260
pub trait TypeExtension {
176261
fn inner_type(&self) -> String;
262+
fn is_non_null(&self) -> bool;
263+
fn is_list_type(&self) -> bool;
264+
fn is_named_type(&self) -> bool;
265+
fn of_type(&self) -> &Type;
177266
}
178267

179268
impl TypeExtension for Type {
@@ -184,6 +273,35 @@ impl TypeExtension for Type {
184273
Type::NonNullType(child) => child.inner_type(),
185274
}
186275
}
276+
277+
fn of_type(&self) -> &Type {
278+
match self {
279+
Type::ListType(child) => child,
280+
Type::NonNullType(child) => child,
281+
Type::NamedType(_) => self,
282+
}
283+
}
284+
285+
fn is_non_null(&self) -> bool {
286+
match self {
287+
Type::NonNullType(_) => true,
288+
_ => false,
289+
}
290+
}
291+
292+
fn is_list_type(&self) -> bool {
293+
match self {
294+
Type::ListType(_) => true,
295+
_ => false,
296+
}
297+
}
298+
299+
fn is_named_type(&self) -> bool {
300+
match self {
301+
Type::NamedType(_) => true,
302+
_ => false,
303+
}
304+
}
187305
}
188306

189307
pub trait ValueExtension {
@@ -247,6 +365,7 @@ pub trait TypeDefinitionExtension {
247365
fn is_input_type(&self) -> bool;
248366
fn is_object_type(&self) -> bool;
249367
fn is_union_type(&self) -> bool;
368+
fn is_interface_type(&self) -> bool;
250369
fn is_enum_type(&self) -> bool;
251370
fn is_scalar_type(&self) -> bool;
252371
fn is_abstract_type(&self) -> bool;
@@ -381,6 +500,13 @@ impl TypeDefinitionExtension for Option<schema::TypeDefinition> {
381500
}
382501
}
383502

503+
fn is_interface_type(&self) -> bool {
504+
match self {
505+
Some(t) => t.is_interface_type(),
506+
_ => false,
507+
}
508+
}
509+
384510
fn is_object_type(&self) -> bool {
385511
match self {
386512
Some(t) => t.is_object_type(),
@@ -444,6 +570,13 @@ impl TypeDefinitionExtension for schema::TypeDefinition {
444570
}
445571
}
446572

573+
fn is_interface_type(&self) -> bool {
574+
match self {
575+
schema::TypeDefinition::Interface(_i) => true,
576+
_ => false,
577+
}
578+
}
579+
447580
fn is_leaf_type(&self) -> bool {
448581
match self {
449582
schema::TypeDefinition::Scalar(_u) => true,

src/lib.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
pub mod ast;
99

1010
pub mod static_graphql {
11-
macro_rules! static_graphql {
11+
macro_rules! static_graphql {
1212
($m:ident, $m2:ident, {$($n:ident,)*}) => {
1313
pub mod $m {
1414
use graphql_parser::$m2 as $m;
@@ -20,16 +20,16 @@ pub mod static_graphql {
2020
};
2121
}
2222

23-
static_graphql!(query, query, {
24-
Document, Value, OperationDefinition, InlineFragment, TypeCondition,
25-
FragmentSpread, Field, Selection, SelectionSet, FragmentDefinition,
26-
Directive, VariableDefinition, Type, Query, Definition, Subscription, Mutation,
27-
});
28-
static_graphql!(schema, schema, {
29-
Field, Directive, InterfaceType, ObjectType, Value, TypeDefinition,
30-
EnumType, Type, Document, ScalarType, InputValue, DirectiveDefinition,
31-
UnionType, InputObjectType, EnumValue, SchemaDefinition,
32-
});
23+
static_graphql!(query, query, {
24+
Document, Value, OperationDefinition, InlineFragment, TypeCondition,
25+
FragmentSpread, Field, Selection, SelectionSet, FragmentDefinition,
26+
Directive, VariableDefinition, Type, Query, Definition, Subscription, Mutation,
27+
});
28+
static_graphql!(schema, schema, {
29+
Field, Directive, InterfaceType, ObjectType, Value, TypeDefinition,
30+
EnumType, Type, Document, ScalarType, InputValue, DirectiveDefinition,
31+
UnionType, InputObjectType, EnumValue, SchemaDefinition,
32+
});
3333
}
3434

3535
pub mod validation;

src/validation/rules/defaults.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::{
66
NoFragmentsCycle, NoUndefinedVariables, NoUnusedFragments, NoUnusedVariables,
77
OverlappingFieldsCanBeMerged, PossibleFragmentSpreads, ProvidedRequiredArguments,
88
SingleFieldSubscriptions, UniqueArgumentNames, UniqueFragmentNames, UniqueOperationNames,
9-
UniqueVariableNames, VariablesAreInputTypes,
9+
UniqueVariableNames, VariablesAreInputTypes, VariablesInAllowedPosition,
1010
};
1111

1212
pub fn default_rules_validation_plan() -> ValidationPlan {
@@ -33,6 +33,7 @@ pub fn default_rules_validation_plan() -> ValidationPlan {
3333
plan.add_rule(Box::new(UniqueVariableNames::new()));
3434
plan.add_rule(Box::new(ProvidedRequiredArguments::new()));
3535
plan.add_rule(Box::new(KnownDirectives::new()));
36+
plan.add_rule(Box::new(VariablesInAllowedPosition::new()));
3637

3738
plan
3839
}

src/validation/rules/fragments_on_composite_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::validation::utils::{ValidationError, ValidationErrorContext};
1616
pub struct FragmentsOnCompositeTypes;
1717

1818
impl FragmentsOnCompositeTypes {
19-
pub fn new() -> Self {
19+
pub fn new() -> Self {
2020
FragmentsOnCompositeTypes
2121
}
2222
}

src/validation/rules/known_directives.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,5 @@ fn misplaced_directives() {
316316
&mut plan,
317317
);
318318

319-
println!("{:?}", get_messages(&errors));
320319
assert_eq!(get_messages(&errors).len(), 11);
321320
}

src/validation/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod unique_fragment_names;
2222
pub mod unique_operation_names;
2323
pub mod unique_variable_names;
2424
pub mod variables_are_input_types;
25+
pub mod variables_in_allowed_position;
2526

2627
pub use self::defaults::*;
2728
pub use self::rule::*;
@@ -47,3 +48,4 @@ pub use self::unique_fragment_names::*;
4748
pub use self::unique_operation_names::*;
4849
pub use self::unique_variable_names::*;
4950
pub use self::variables_are_input_types::*;
51+
pub use self::variables_in_allowed_position::*;

src/validation/rules/no_unused_variables.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ fn multiple_variables_not_used() {
357357
let messages = get_messages(&errors);
358358

359359
assert_eq!(messages.len(), 2);
360-
println!("messages: {:?}", messages);
361360
assert!(messages.contains(&&"Variable \"$a\" is never used in operation \"Foo\".".to_owned()));
362361
assert!(messages.contains(&&"Variable \"$c\" is never used in operation \"Foo\".".to_owned()));
363362
}

src/validation/rules/overlapping_fields_can_be_merged.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1798,7 +1798,6 @@ fn finds_invalid_case_even_with_immediately_recursive_fragment() {
17981798
);
17991799

18001800
let messages = get_messages(&errors);
1801-
println!("{:?}", messages);
18021801
assert_eq!(messages.len(), 1);
18031802
assert_eq!(messages, vec![
18041803
"Fields \"fido\" conflict because \"name\" and \"nickname\" are different fields. Use different aliases on the fields to fetch both if this was intentional."

0 commit comments

Comments
 (0)