55 * LICENSE file in the root directory of this source tree.
66 */
77
8+ use std:: collections:: HashMap ;
89use std:: sync:: Arc ;
910
1011use :: errors:: try_all;
1112use common:: Diagnostic ;
1213use common:: DiagnosticTag ;
1314use common:: DiagnosticsResult ;
1415use common:: DirectiveName ;
16+ use common:: Location ;
1517use common:: NamedItem ;
1618use errors:: try2;
1719use graphql_ir:: Field ;
@@ -20,6 +22,7 @@ use graphql_ir::Program;
2022use graphql_ir:: Selection ;
2123use graphql_ir:: Validator ;
2224use graphql_ir:: reexport:: Intern ;
25+ use intern:: string_key:: StringKey ;
2326use lazy_static:: lazy_static;
2427use schema:: SDLSchema ;
2528use schema:: Schema ;
@@ -47,19 +50,53 @@ pub fn disallow_required_on_non_null_field(program: &Program) -> DiagnosticsResu
4750 }
4851}
4952
53+ type FieldPath = Vec < StringKey > ;
54+
5055struct DisallowRequiredOnNonNullField < ' a > {
5156 schema : & ' a Arc < SDLSchema > ,
5257 warnings : Vec < Diagnostic > ,
58+ path : FieldPath ,
59+ modifiable_fields : HashMap < FieldPath , Action > ,
5360}
5461
5562impl < ' a > DisallowRequiredOnNonNullField < ' a > {
5663 fn new ( schema : & ' a Arc < SDLSchema > ) -> Self {
5764 Self {
5865 schema,
5966 warnings : vec ! [ ] ,
67+ path : vec ! [ ] ,
68+ modifiable_fields : HashMap :: new ( ) ,
6069 }
6170 }
6271
72+ // Tracks field required-directive removal eligibility. If a field's required directive,
73+ // is not removable, it remains not removable forever. Otherwise it is removable and we
74+ // accumulate the locations where it can be removed... unless it becomes not removable
75+ // later on.
76+ fn update_field_action ( & mut self , new_action : Action ) {
77+ let mut path = self . path . clone ( ) ;
78+ path. reverse ( ) ;
79+ let existing_action = self . modifiable_fields . get ( & path) ;
80+ self . modifiable_fields . insert (
81+ path,
82+ match existing_action {
83+ None => new_action,
84+ Some ( Action :: NotRemovable ) => {
85+ // already not removable, keep it that way
86+ Action :: NotRemovable
87+ }
88+ Some ( Action :: Removable ( list) ) => match new_action {
89+ Action :: NotRemovable => Action :: NotRemovable ,
90+ Action :: Removable ( new_list) => {
91+ let mut new_list = new_list. clone ( ) ;
92+ new_list. extend ( list. clone ( ) ) ;
93+ Action :: Removable ( new_list)
94+ }
95+ } ,
96+ } ,
97+ ) ;
98+ }
99+
63100 fn validate_required_field (
64101 & mut self ,
65102 field : & Arc < impl Field > ,
@@ -81,11 +118,10 @@ impl<'a> DisallowRequiredOnNonNullField<'a> {
81118 . type_
82119 . is_non_null ( )
83120 {
84- self . warnings . push ( Diagnostic :: hint_with_data (
85- ValidationMessageWithData :: RequiredOnNonNull ,
86- required_directive. unwrap ( ) . location ,
87- vec ! [ DiagnosticTag :: UNNECESSARY ] ,
88- ) ) ;
121+ self . update_field_action ( Action :: Removable ( vec ! [ Message {
122+ message: ValidationMessageWithData :: RequiredOnNonNull ,
123+ location: required_directive. unwrap( ) . location,
124+ } ] ) ) ;
89125 } else if self
90126 . schema
91127 . field ( field. definition ( ) . item )
@@ -94,11 +130,12 @@ impl<'a> DisallowRequiredOnNonNullField<'a> {
94130 . is_some ( )
95131 {
96132 // @required on a semantically-non-null field is unnecessary
97- self . warnings . push ( Diagnostic :: hint_with_data (
98- ValidationMessageWithData :: RequiredOnSemanticNonNull ,
99- required_directive. unwrap ( ) . location ,
100- vec ! [ DiagnosticTag :: UNNECESSARY ] ,
101- ) ) ;
133+ self . update_field_action ( Action :: Removable ( vec ! [ Message {
134+ message: ValidationMessageWithData :: RequiredOnSemanticNonNull ,
135+ location: required_directive. unwrap( ) . location,
136+ } ] ) ) ;
137+ } else {
138+ self . update_field_action ( Action :: NotRemovable ) ;
102139 }
103140
104141 Ok ( ( ) )
@@ -125,22 +162,52 @@ impl<'a> DisallowRequiredOnNonNullField<'a> {
125162 None => self . validate_required_field ( linked_field, errors_are_caught) ,
126163 } ;
127164
165+ self . path . push ( linked_field. alias_or_name ( self . schema ) ) ;
166+
128167 let selection_result =
129168 self . validate_selection_fields ( & linked_field. selections , errors_are_caught) ;
130169
131170 try2 ( field_result, selection_result) ?;
171+ self . path . pop ( ) ;
132172 Ok ( ( ) )
133173 }
134174 Selection :: ScalarField ( scalar_field) => {
135175 self . validate_required_field ( scalar_field, errors_are_caught)
136176 }
137177 Selection :: InlineFragment ( fragment) => {
138- self . validate_selection_fields ( & fragment. selections , errors_are_caught)
178+ if let Ok ( Some ( alias) ) = fragment. alias ( self . schema ) {
179+ self . path . push ( alias. item ) ;
180+ let result =
181+ self . validate_selection_fields ( & fragment. selections , errors_are_caught) ;
182+ self . path . pop ( ) ;
183+ result
184+ } else {
185+ self . validate_selection_fields ( & fragment. selections , errors_are_caught)
186+ }
139187 }
140188 _ => Ok ( ( ) ) ,
141189 } ) ) ?;
142190 Ok ( ( ) )
143191 }
192+
193+ fn modifiable_fields_to_warnings ( & mut self ) {
194+ for ( path, action) in self . modifiable_fields . iter ( ) {
195+ match action {
196+ Action :: NotRemovable => { }
197+ Action :: Removable ( message_list) => {
198+ for message in message_list {
199+ self . warnings . push ( Diagnostic :: hint_with_data (
200+ message. message . clone ( ) ,
201+ message. location ,
202+ vec ! [ DiagnosticTag :: UNNECESSARY ] ,
203+ ) ) ;
204+ }
205+ }
206+ }
207+
208+ println ! ( "path: {:?}" , path) ;
209+ }
210+ }
144211}
145212
146213impl Validator for DisallowRequiredOnNonNullField < ' _ > {
@@ -149,22 +216,41 @@ impl Validator for DisallowRequiredOnNonNullField<'_> {
149216 const VALIDATE_DIRECTIVES : bool = false ;
150217
151218 fn validate_fragment ( & mut self , fragment : & FragmentDefinition ) -> DiagnosticsResult < ( ) > {
219+ self . modifiable_fields . clear ( ) ;
152220 let throw_on_field_error_directive =
153221 fragment. directives . named ( * THROW_ON_FIELD_ERROR_DIRECTIVE ) ;
154222
155223 let has_throw_on_field_error_directive = throw_on_field_error_directive. is_some ( ) ;
156224
157- self . validate_selection_fields ( & fragment. selections , has_throw_on_field_error_directive)
225+ let ret = self
226+ . validate_selection_fields ( & fragment. selections , has_throw_on_field_error_directive) ;
227+ self . modifiable_fields_to_warnings ( ) ;
228+ ret
158229 }
159230
160231 fn validate_operation (
161232 & mut self ,
162233 operation : & graphql_ir:: OperationDefinition ,
163234 ) -> DiagnosticsResult < ( ) > {
235+ self . modifiable_fields . clear ( ) ;
164236 let throw_on_field_error_directive =
165237 operation. directives . named ( * THROW_ON_FIELD_ERROR_DIRECTIVE ) ;
166238
167239 let has_throw_on_field_error_directive = throw_on_field_error_directive. is_some ( ) ;
168- self . validate_selection_fields ( & operation. selections , has_throw_on_field_error_directive)
240+ let result = self
241+ . validate_selection_fields ( & operation. selections , has_throw_on_field_error_directive) ;
242+ self . modifiable_fields_to_warnings ( ) ;
243+ result
169244 }
170245}
246+
247+ #[ derive( Clone , Debug ) ]
248+ struct Message {
249+ location : Location ,
250+ message : ValidationMessageWithData ,
251+ }
252+
253+ enum Action {
254+ NotRemovable ,
255+ Removable ( Vec < Message > ) ,
256+ }
0 commit comments