2
2
3
3
using System ;
4
4
using System . Collections . Generic ;
5
+ using System . Diagnostics ;
6
+ using System . Linq ;
7
+ using HotChocolate . Language ;
5
8
using HotChocolate . Types ;
6
9
using static HotChocolate . Configuration . Validation . TypeValidationHelper ;
7
10
using static HotChocolate . Utilities . ErrorHelper ;
@@ -15,18 +18,104 @@ public void Validate(
15
18
IReadOnlySchemaOptions options ,
16
19
ICollection < ISchemaError > errors )
17
20
{
18
- if ( options . StrictValidation )
21
+ if ( ! options . StrictValidation )
19
22
{
20
- List < string > ? names = null ;
23
+ return ;
24
+ }
25
+
26
+ List < string > ? names = null ;
27
+ CycleValidationContext cycleValidationContext = new ( )
28
+ {
29
+ Visited = new ( ) ,
30
+ CycleStartIndex = new ( ) ,
31
+ Errors = errors ,
32
+ FieldPath = new ( ) ,
33
+ } ;
34
+
35
+ foreach ( var type in typeSystemObjects )
36
+ {
37
+ if ( type is not InputObjectType inputType )
38
+ {
39
+ continue ;
40
+ }
41
+
42
+ EnsureTypeHasFields ( inputType , errors ) ;
43
+ EnsureFieldNamesAreValid ( inputType , errors ) ;
44
+ EnsureOneOfFieldsAreValid ( inputType , errors , ref names ) ;
45
+ EnsureFieldDeprecationIsValid ( inputType , errors ) ;
46
+ TryReachCycleRecursively ( cycleValidationContext , inputType ) ;
47
+
48
+ cycleValidationContext . CycleStartIndex . Clear ( ) ;
49
+ }
50
+ }
51
+
52
+ private struct CycleValidationContext
53
+ {
54
+ public HashSet < InputObjectType > Visited { get ; set ; }
55
+ public Dictionary < InputObjectType , int > CycleStartIndex { get ; set ; }
56
+ public ICollection < ISchemaError > Errors { get ; set ; }
57
+ public List < string > FieldPath { get ; set ; }
58
+ }
59
+
60
+ // https://github.com/IvanGoncharov/graphql-js/blob/408bcda9c88df85e039f5d072011b1cb465fe830/src/type/validate.js#L535
61
+ private static void TryReachCycleRecursively (
62
+ in CycleValidationContext context ,
63
+ InputObjectType type )
64
+ {
65
+ if ( ! context . Visited . Add ( type ) )
66
+ {
67
+ return ;
68
+ }
69
+
70
+ context . CycleStartIndex [ type ] = context . FieldPath . Count ;
71
+
72
+ foreach ( var field in type . Fields )
73
+ {
74
+ var unwrappedType = UnwrapCompletelyIfRequired ( field . Type ) ;
75
+ if ( unwrappedType is not InputObjectType inputObjectType )
76
+ {
77
+ continue ;
78
+ }
79
+
80
+ context . FieldPath . Add ( field . Name ) ;
81
+ if ( context . CycleStartIndex . TryGetValue ( inputObjectType , out var cycleIndex ) )
82
+ {
83
+ var cyclePath = context . FieldPath . Skip ( cycleIndex ) ;
84
+ context . Errors . Add (
85
+ InputObjectMustNotHaveRecursiveNonNullableReferencesToSelf ( type , cyclePath ) ) ;
86
+ }
87
+ else
88
+ {
89
+ TryReachCycleRecursively ( context , inputObjectType ) ;
90
+ }
91
+ context . FieldPath . Pop ( ) ;
92
+ }
93
+
94
+ context . CycleStartIndex . Remove ( type ) ;
95
+ }
96
+
97
+ private static IType ? UnwrapCompletelyIfRequired ( IType type )
98
+ {
99
+ while ( true )
100
+ {
101
+ if ( type . Kind == TypeKind . NonNull )
102
+ {
103
+ type = ( ( NonNullType ) type ) . Type ;
104
+ }
105
+ else
106
+ {
107
+ return null ;
108
+ }
21
109
22
- foreach ( var type in typeSystemObjects )
110
+ switch ( type . Kind )
23
111
{
24
- if ( type is InputObjectType inputType )
112
+ case TypeKind . List :
25
113
{
26
- EnsureTypeHasFields ( inputType , errors ) ;
27
- EnsureFieldNamesAreValid ( inputType , errors ) ;
28
- EnsureOneOfFieldsAreValid ( inputType , errors , ref names ) ;
29
- EnsureFieldDeprecationIsValid ( inputType , errors ) ;
114
+ return null ;
115
+ }
116
+ default :
117
+ {
118
+ return type ;
30
119
}
31
120
}
32
121
}
@@ -37,30 +126,33 @@ private static void EnsureOneOfFieldsAreValid(
37
126
ICollection < ISchemaError > errors ,
38
127
ref List < string > ? temp )
39
128
{
40
- if ( type . Directives . ContainsDirective ( WellKnownDirectives . OneOf ) )
129
+ if ( ! type . Directives . ContainsDirective ( WellKnownDirectives . OneOf ) )
41
130
{
42
- temp ??= new List < string > ( ) ;
131
+ return ;
132
+ }
43
133
44
- foreach ( var field in type . Fields )
45
- {
46
- if ( field . Type . Kind is TypeKind . NonNull || field . DefaultValue is not null )
47
- {
48
- temp . Add ( field . Name ) ;
49
- }
50
- }
134
+ temp ??= new List < string > ( ) ;
51
135
52
- if ( temp . Count > 0 )
136
+ foreach ( var field in type . Fields )
137
+ {
138
+ if ( field . Type . Kind is TypeKind . NonNull || field . DefaultValue is not null )
53
139
{
54
- var fieldNames = new string [ temp . Count ] ;
140
+ temp . Add ( field . Name ) ;
141
+ }
142
+ }
55
143
56
- for ( var i = 0 ; i < temp . Count ; i ++ )
57
- {
58
- fieldNames [ i ] = temp [ i ] ;
59
- }
144
+ if ( temp . Count == 0 )
145
+ {
146
+ return ;
147
+ }
60
148
61
- temp . Clear ( ) ;
62
- errors . Add ( OneofInputObjectMustHaveNullableFieldsWithoutDefaults ( type , fieldNames ) ) ;
63
- }
149
+ var fieldNames = new string [ temp . Count ] ;
150
+ for ( var i = 0 ; i < temp . Count ; i ++ )
151
+ {
152
+ fieldNames [ i ] = temp [ i ] ;
64
153
}
154
+
155
+ temp . Clear ( ) ;
156
+ errors . Add ( OneofInputObjectMustHaveNullableFieldsWithoutDefaults ( type , fieldNames ) ) ;
65
157
}
66
158
}
0 commit comments