Skip to content

Commit 0a647d8

Browse files
committed
added PossibleFragmentSpreads
fixes for extensions
1 parent 9ce5a94 commit 0a647d8

File tree

8 files changed

+710
-9
lines changed

8 files changed

+710
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ cargo add graphql-tools
5454
- [x] UniqueFragmentNames
5555
- [x] KnownFragmentNames
5656
- [x] NoUnusedFragments
57-
- [ ] PossibleFragmentSpreads
57+
- [x] PossibleFragmentSpreads
5858
- [x] NoFragmentCycles
5959
- [ ] UniqueVariableNames
6060
- [ ] NoUndefinedVariables

src/ast/collect_fields.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashMap;
22

33
use super::{AbstractTypeDefinitionExtension, TypeInfoRegistry};
4-
use crate::ast::ext::{TypeDefinitionExtension, UnionTypeExtension};
4+
use crate::ast::ext::{SubTypeExtension, TypeDefinitionExtension};
55
use crate::static_graphql::{
66
query::{self, Selection, TypeCondition},
77
schema::{self, TypeDefinition},

src/ast/ext.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::static_graphql::schema::{
33
self, Field, InterfaceType, ObjectType, TypeDefinition, UnionType,
44
};
55

6-
use super::{get_named_type, TypeInfoElementRef};
6+
use super::{get_named_type, TypeInfoElementRef, TypeInfoRegistry};
77

88
pub trait AstNodeWithFields {
99
fn find_field(&self, name: String) -> Option<&Field>;
@@ -70,6 +70,14 @@ impl CompositeType {
7070
_ => None,
7171
}
7272
}
73+
74+
pub fn as_type_definition(&self) -> schema::TypeDefinition {
75+
match self {
76+
CompositeType::Object(o) => schema::TypeDefinition::Object(o.clone()),
77+
CompositeType::Interface(o) => schema::TypeDefinition::Interface(o.clone()),
78+
CompositeType::Union(o) => schema::TypeDefinition::Union(o.clone()),
79+
}
80+
}
7381
}
7482

7583
pub trait AbstractTypeDefinitionExtension {
@@ -86,6 +94,7 @@ pub trait TypeDefinitionExtension {
8694

8795
pub trait ImplementingInterfaceExtension {
8896
fn interfaces(&self) -> Vec<String>;
97+
fn has_sub_type(&self, other_type: &TypeDefinition) -> bool;
8998
}
9099

91100
impl ImplementingInterfaceExtension for TypeDefinition {
@@ -96,25 +105,86 @@ impl ImplementingInterfaceExtension for TypeDefinition {
96105
_ => vec![],
97106
}
98107
}
108+
109+
fn has_sub_type(&self, other_type: &TypeDefinition) -> bool {
110+
match self {
111+
TypeDefinition::Interface(interface_type) => {
112+
return interface_type.is_implemented_by(other_type)
113+
}
114+
TypeDefinition::Union(union_type) => {
115+
return union_type.has_sub_type(&other_type.name())
116+
}
117+
_ => return false,
118+
}
119+
}
120+
}
121+
122+
pub trait PossibleTypesExtension<'a> {
123+
fn possible_types(&self, type_info_registry: &TypeInfoRegistry) -> Vec<ObjectType>;
124+
}
125+
126+
impl<'a> PossibleTypesExtension<'a> for TypeDefinition {
127+
fn possible_types(&self, type_info_registry: &TypeInfoRegistry) -> Vec<ObjectType> {
128+
match self {
129+
TypeDefinition::Object(_) => vec![],
130+
TypeDefinition::InputObject(_) => vec![],
131+
TypeDefinition::Enum(_) => vec![],
132+
TypeDefinition::Scalar(_) => vec![],
133+
TypeDefinition::Interface(i) => type_info_registry
134+
.type_by_name
135+
.iter()
136+
.filter_map(|(_type_name, type_def)| {
137+
if let TypeDefinition::Object(o) = type_def {
138+
if i.is_implemented_by(*type_def) {
139+
return Some(o.clone());
140+
}
141+
}
142+
143+
None
144+
})
145+
.collect(),
146+
TypeDefinition::Union(u) => u
147+
.types
148+
.iter()
149+
.filter_map(|type_name| {
150+
if let Some(TypeDefinition::Object(o)) =
151+
type_info_registry.type_by_name.get(type_name)
152+
{
153+
return Some(o.clone());
154+
}
155+
156+
None
157+
})
158+
.collect(),
159+
}
160+
}
99161
}
100162

101163
impl ImplementingInterfaceExtension for InterfaceType {
102164
fn interfaces(&self) -> Vec<String> {
103165
self.implements_interfaces.clone()
104166
}
167+
168+
fn has_sub_type(&self, other_type: &TypeDefinition) -> bool {
169+
self.is_implemented_by(other_type)
170+
}
105171
}
106172

107173
impl ImplementingInterfaceExtension for ObjectType {
108174
fn interfaces(&self) -> Vec<String> {
109175
self.implements_interfaces.clone()
110176
}
177+
178+
fn has_sub_type(&self, _other_type: &TypeDefinition) -> bool {
179+
false
180+
}
111181
}
112182

113-
pub trait UnionTypeExtension {
183+
pub trait SubTypeExtension {
114184
fn has_sub_type(&self, other_type_name: &String) -> bool;
115185
}
116186

117-
impl UnionTypeExtension for UnionType {
187+
impl SubTypeExtension for UnionType {
118188
fn has_sub_type(&self, other_type_name: &String) -> bool {
119189
self.types.iter().find(|v| other_type_name.eq(*v)).is_some()
120190
}

src/ast/utils.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use crate::static_graphql::schema;
1+
use crate::ast::ext::{
2+
ImplementingInterfaceExtension, PossibleTypesExtension, TypeDefinitionExtension,
3+
};
4+
use crate::static_graphql::schema::{self, TypeDefinition};
5+
6+
use super::TypeInfoRegistry;
27

38
pub struct DefaultVisitorContext;
49

@@ -12,10 +17,56 @@ pub fn find_schema_definition(schema: &schema::Document) -> Option<&schema::Sche
1217
})
1318
}
1419

20+
/**
21+
* Extracts nested NamedType from a potentially recursive wrapped definition.
22+
*
23+
* Example: Returns String from [String] or String!
24+
*/
1525
pub fn get_named_type(t: &schema::Type) -> String {
1626
match t {
1727
schema::Type::NamedType(name) => name.clone(),
1828
schema::Type::ListType(inner_type) => get_named_type(inner_type),
1929
schema::Type::NonNullType(inner_type) => get_named_type(inner_type),
2030
}
2131
}
32+
33+
/**
34+
* Provided two composite types, determine if they "overlap". Two composite
35+
* types overlap when the Sets of possible concrete types for each intersect.
36+
*
37+
* This is often used to determine if a fragment of a given type could possibly
38+
* be visited in a context of another type.
39+
*
40+
* This function is commutative.
41+
*/
42+
pub fn do_types_overlap(
43+
type_info_registry: &TypeInfoRegistry,
44+
t1: &schema::TypeDefinition,
45+
t2: &schema::TypeDefinition,
46+
) -> bool {
47+
if t1.name().eq(&t2.name()) {
48+
return true;
49+
}
50+
51+
if t1.is_abstract_type() {
52+
if t2.is_abstract_type() {
53+
let possible_types = t1.possible_types(type_info_registry);
54+
55+
return possible_types
56+
.into_iter()
57+
.filter(|possible_type| {
58+
t2.has_sub_type(&TypeDefinition::Object(possible_type.clone()))
59+
})
60+
.count()
61+
> 0;
62+
}
63+
64+
return t1.has_sub_type(t2);
65+
}
66+
67+
if t2.is_abstract_type() {
68+
return t2.has_sub_type(t1);
69+
}
70+
71+
false
72+
}

src/validation/rules/defaults.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use crate::validation::validate::ValidationPlan;
22

33
use super::{
44
FieldsOnCorrectType, FragmentsOnCompositeTypes, KnownFragmentNames, KnownTypeNames,
5-
LeafFieldSelections, LoneAnonymousOperation, NoUnusedFragments, OverlappingFieldsCanBeMerged,
6-
SingleFieldSubscriptions, UniqueFragmentNames, UniqueOperationNames, VariablesAreInputTypes,
5+
LeafFieldSelections, LoneAnonymousOperation, NoFragmentsCycle, NoUnusedFragments,
6+
OverlappingFieldsCanBeMerged, PossibleFragmentSpreads, SingleFieldSubscriptions,
7+
UniqueFragmentNames, UniqueOperationNames, VariablesAreInputTypes,
78
};
89

910
pub fn default_rules_validation_plan() -> ValidationPlan {
@@ -21,6 +22,8 @@ pub fn default_rules_validation_plan() -> ValidationPlan {
2122
plan.add_rule(Box::new(KnownFragmentNames {}));
2223
plan.add_rule(Box::new(NoUnusedFragments {}));
2324
plan.add_rule(Box::new(OverlappingFieldsCanBeMerged {}));
25+
plan.add_rule(Box::new(NoFragmentsCycle {}));
26+
plan.add_rule(Box::new(PossibleFragmentSpreads {}));
2427

2528
plan
2629
}

src/validation/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod lone_anonymous_operation;
88
pub mod no_fragments_cycle;
99
pub mod no_unused_fragments;
1010
pub mod overlapping_fields_can_be_merged;
11+
pub mod possible_fragment_spreads;
1112
/// Utilities validating GraphQL documents/operations
1213
pub mod rule;
1314
pub mod single_field_subscriptions;
@@ -25,6 +26,7 @@ pub use self::lone_anonymous_operation::*;
2526
pub use self::no_fragments_cycle::*;
2627
pub use self::no_unused_fragments::*;
2728
pub use self::overlapping_fields_can_be_merged::*;
29+
pub use self::possible_fragment_spreads::*;
2830
pub use self::rule::*;
2931
pub use self::single_field_subscriptions::*;
3032
pub use self::unique_fragment_names::*;

src/validation/rules/no_fragments_cycle.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ pub struct NoFragmentsCycle;
2020

2121
struct NoFragmentsCycleHelper<'a> {
2222
visited_fragments: HashMap<String, bool>,
23-
2423
errors_context: ValidationErrorContext<'a>,
2524
}
2625

0 commit comments

Comments
 (0)