@@ -12,6 +12,7 @@ use spk_schema_foundation::option_map::Stringified;
12
12
use spk_schema_foundation:: version:: { Compat , IncompatibleReason , VarOptionProblem } ;
13
13
use spk_schema_foundation:: IsDefault ;
14
14
use spk_schema_ident:: { NameAndValue , PinnableValue , RangeIdent } ;
15
+ use struct_field_names_as_array:: FieldNamesAsArray ;
15
16
16
17
use crate :: foundation:: name:: { OptName , OptNameBuf , PkgName , PkgNameBuf } ;
17
18
use crate :: foundation:: version:: { CompatRule , Compatibility } ;
@@ -25,7 +26,7 @@ use crate::ident::{
25
26
RequestedBy ,
26
27
VarRequest ,
27
28
} ;
28
- use crate :: { Error , Result } ;
29
+ use crate :: { Error , Lint , LintedItem , Lints , Result , UnknownKey } ;
29
30
30
31
#[ cfg( test) ]
31
32
#[ path = "./option_test.rs" ]
@@ -190,142 +191,178 @@ impl TryFrom<Request> for Opt {
190
191
}
191
192
}
192
193
194
+ /// This visitor captures all fields that could be valid
195
+ /// for any option, before deciding at the end which variant
196
+ /// to actually build. We ignore any unrecognized field anyway,
197
+ /// but additionally any field that's recognized must be valid
198
+ /// even if it's not going to be used.
199
+ ///
200
+ /// The purpose of this setup is to enable more meaningful errors
201
+ /// for invalid values that contain original source positions. In
202
+ /// order to achieve this we must parse and validate each field with
203
+ /// the appropriate type as they are visited - which disqualifies the
204
+ /// existing approach to untagged enums which read all fields first
205
+ /// and then goes back and checks them once the variant is determined
206
+ #[ derive( Default , FieldNamesAsArray ) ]
207
+ struct UncheckedOptVisitor {
208
+ // PkgOpt
209
+ pkg : Option < PkgNameWithComponents > ,
210
+ prerelease_policy : Option < PreReleasePolicy > ,
211
+
212
+ // VarOpt
213
+ var : Option < OptNameBuf > ,
214
+ choices : Option < IndexSet < String > > ,
215
+ inheritance : Option < Inheritance > ,
216
+ compat : Option < Compat > ,
217
+
218
+ // Both
219
+ default : Option < String > ,
220
+ value : Option < String > ,
221
+ description : Option < String > ,
222
+
223
+ // Lints
224
+ #[ field_names_as_array( skip) ]
225
+ lints : Vec < Lint > ,
226
+ }
227
+
228
+ struct CheckedOptVisitor {
229
+ opt : Opt ,
230
+ lints : Vec < Lint > ,
231
+ }
232
+
233
+ impl Lints for CheckedOptVisitor {
234
+ fn lints ( & mut self ) -> Vec < Lint > {
235
+ std:: mem:: take ( & mut self . lints )
236
+ }
237
+ }
238
+
239
+ impl From < CheckedOptVisitor > for Opt {
240
+ fn from ( value : CheckedOptVisitor ) -> Self {
241
+ value. opt
242
+ }
243
+ }
244
+
193
245
impl < ' de > Deserialize < ' de > for Opt {
194
246
fn deserialize < D > ( deserializer : D ) -> std:: result:: Result < Self , D :: Error >
195
247
where
196
- D : serde:: Deserializer < ' de > ,
248
+ D : serde:: de :: Deserializer < ' de > ,
197
249
{
198
- /// This visitor captures all fields that could be valid
199
- /// for any option, before deciding at the end which variant
200
- /// to actually build. We ignore any unrecognized field anyway,
201
- /// but additionally any field that's recognized must be valid
202
- /// even if it's not going to be used.
203
- ///
204
- /// The purpose of this setup is to enable more meaningful errors
205
- /// for invalid values that contain original source positions. In
206
- /// order to achieve this we must parse and validate each field with
207
- /// the appropriate type as they are visited - which disqualifies the
208
- /// existing approach to untagged enums which read all fields first
209
- /// and then goes back and checks them once the variant is determined
210
- #[ derive( Default ) ]
211
- struct OptVisitor {
212
- // PkgOpt
213
- pkg : Option < PkgNameWithComponents > ,
214
- prerelease_policy : Option < PreReleasePolicy > ,
215
-
216
- // VarOpt
217
- var : Option < OptNameBuf > ,
218
- choices : Option < IndexSet < String > > ,
219
- inheritance : Option < Inheritance > ,
220
- compat : Option < Compat > ,
221
-
222
- // Both
223
- default : Option < String > ,
224
- value : Option < String > ,
225
- description : Option < String > ,
226
- }
250
+ Ok ( deserializer
251
+ . deserialize_map ( UncheckedOptVisitor :: default ( ) ) ?
252
+ . into ( ) )
253
+ }
254
+ }
227
255
228
- impl < ' de > serde:: de:: Visitor < ' de > for OptVisitor {
229
- type Value = Opt ;
256
+ impl < ' de > Deserialize < ' de > for LintedItem < Opt > {
257
+ fn deserialize < D > ( deserializer : D ) -> std:: result:: Result < Self , D :: Error >
258
+ where
259
+ D : serde:: de:: Deserializer < ' de > ,
260
+ {
261
+ Ok ( deserializer
262
+ . deserialize_map ( UncheckedOptVisitor :: default ( ) ) ?
263
+ . into ( ) )
264
+ }
265
+ }
230
266
231
- fn expecting ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
232
- f. write_str ( "a pkg or var option" )
233
- }
267
+ impl < ' de > serde:: de:: Visitor < ' de > for UncheckedOptVisitor {
268
+ type Value = CheckedOptVisitor ;
234
269
235
- fn visit_map < A > ( mut self , mut map : A ) -> std:: result:: Result < Self :: Value , A :: Error >
236
- where
237
- A : serde:: de:: MapAccess < ' de > ,
238
- {
239
- let check_existing_default = |v : & OptVisitor | -> std:: result:: Result < ( ) , A :: Error > {
240
- if v. default . is_some ( ) {
241
- Err ( serde:: de:: Error :: custom ( "option cannot specify both the 'default' field and a default value in the form <name>/<default>" ) )
242
- } else {
243
- Ok ( ( ) )
270
+ fn expecting ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
271
+ f. write_str ( "a pkg or var option" )
272
+ }
273
+
274
+ fn visit_map < A > ( mut self , mut map : A ) -> std:: result:: Result < Self :: Value , A :: Error >
275
+ where
276
+ A : serde:: de:: MapAccess < ' de > ,
277
+ {
278
+ let check_existing_default =
279
+ |v : & UncheckedOptVisitor | -> std:: result:: Result < ( ) , A :: Error > {
280
+ if v. default . is_some ( ) {
281
+ Err ( serde:: de:: Error :: custom ( "option cannot specify both the 'default' field and a default value in the form <name>/<default>" ) )
282
+ } else {
283
+ Ok ( ( ) )
284
+ }
285
+ } ;
286
+
287
+ while let Some ( mut key) = map. next_key :: < Stringified > ( ) ? {
288
+ key. make_ascii_lowercase ( ) ;
289
+ match key. as_str ( ) {
290
+ "pkg" => {
291
+ let pkg_name_with_components: PkgNameWithComponents = map. next_value ( ) ?;
292
+ if pkg_name_with_components. default . is_some ( ) {
293
+ check_existing_default ( & self ) ?;
294
+ self . default . clone_from ( & pkg_name_with_components. default ) ;
244
295
}
245
- } ;
246
-
247
- while let Some ( mut key) = map. next_key :: < Stringified > ( ) ? {
248
- key. make_ascii_lowercase ( ) ;
249
- match key. as_str ( ) {
250
- "pkg" => {
251
- let pkg_name_with_components: PkgNameWithComponents =
252
- map. next_value ( ) ?;
253
- if pkg_name_with_components. default . is_some ( ) {
254
- check_existing_default ( & self ) ?;
255
- self . default . clone_from ( & pkg_name_with_components. default ) ;
256
- }
257
- self . pkg = Some ( pkg_name_with_components) ;
258
- }
259
- "prereleasepolicy" => {
260
- self . prerelease_policy = Some ( map. next_value :: < PreReleasePolicy > ( ) ?)
261
- }
262
- "var" => {
263
- let NameAndValue ( name, value) = map. next_value ( ) ?;
264
- self . var = Some ( name) ;
265
- if value. is_some ( ) {
266
- check_existing_default ( & self ) ?;
267
- }
268
- self . default = value;
269
- }
270
- "choices" => {
271
- self . choices = Some (
272
- map. next_value :: < Vec < Stringified > > ( ) ?
273
- . into_iter ( )
274
- . map ( |s| s. 0 )
275
- . collect ( ) ,
276
- )
277
- }
278
- "inheritance" => self . inheritance = Some ( map. next_value :: < Inheritance > ( ) ?) ,
279
- "default" => {
280
- check_existing_default ( & self ) ?;
281
- self . default = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ;
282
- }
283
- "static" => self . value = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ,
284
- "description" => {
285
- self . description = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ;
286
- }
287
- "compat" => {
288
- self . compat = Some ( map. next_value :: < Compat > ( ) ?) ;
289
- }
290
- _ => {
291
- // unrecognized fields are explicitly ignored in case
292
- // they were added in a newer version of spk. We assume
293
- // that if the api has not been versioned then the desire
294
- // is to continue working in this older version
295
- map. next_value :: < serde:: de:: IgnoredAny > ( ) ?;
296
- }
296
+ self . pkg = Some ( pkg_name_with_components) ;
297
+ }
298
+ "prereleasepolicy" => {
299
+ self . prerelease_policy = Some ( map. next_value :: < PreReleasePolicy > ( ) ?)
300
+ }
301
+ "var" => {
302
+ let NameAndValue ( name, value) = map. next_value ( ) ?;
303
+ self . var = Some ( name) ;
304
+ if value. is_some ( ) {
305
+ check_existing_default ( & self ) ?;
297
306
}
307
+ self . default = value;
298
308
}
299
-
300
- match ( self . pkg , self . var ) {
301
- ( Some ( pkg) , None ) => Ok ( Opt :: Pkg ( PkgOpt {
302
- pkg : pkg. name ,
303
- components : pkg. components ,
304
- prerelease_policy : self . prerelease_policy ,
305
- required_compat : Default :: default ( ) ,
306
- default : self . default . unwrap_or_default ( ) ,
307
- value : self . value ,
308
- } ) ) ,
309
- ( None , Some ( var) ) =>Ok ( Opt :: Var ( VarOpt {
310
- var,
311
- choices : self . choices . unwrap_or_default ( ) ,
312
- inheritance : self . inheritance . unwrap_or_default ( ) ,
313
- default : self . default . unwrap_or_default ( ) ,
314
- description : self . description ,
315
- compat : self . compat ,
316
- value : self . value ,
317
- } ) ) ,
318
- ( Some ( _) , Some ( _) ) => Err ( serde:: de:: Error :: custom (
319
- "could not determine option type, it may only contain one of the `pkg` or `var` fields"
320
- ) ) ,
321
- ( None , None ) => Err ( serde:: de:: Error :: custom (
322
- "could not determine option type, it must include either a `pkg` or `var` field"
323
- ) ) ,
309
+ "choices" => {
310
+ self . choices = Some (
311
+ map. next_value :: < Vec < Stringified > > ( ) ?
312
+ . into_iter ( )
313
+ . map ( |s| s. 0 )
314
+ . collect ( ) ,
315
+ )
316
+ }
317
+ "inheritance" => self . inheritance = Some ( map. next_value :: < Inheritance > ( ) ?) ,
318
+ "default" => {
319
+ check_existing_default ( & self ) ?;
320
+ self . default = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ;
321
+ }
322
+ "static" => self . value = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ,
323
+ "description" => {
324
+ self . description = Some ( map. next_value :: < Stringified > ( ) ?. 0 ) ;
325
+ }
326
+ "compat" => {
327
+ self . compat = Some ( map. next_value :: < Compat > ( ) ?) ;
328
+ }
329
+ unknown_key => {
330
+ self . lints . push ( Lint :: Key ( UnknownKey :: new (
331
+ unknown_key,
332
+ UncheckedOptVisitor :: FIELD_NAMES_AS_ARRAY . to_vec ( ) ,
333
+ ) ) ) ;
334
+ map. next_value :: < serde:: de:: IgnoredAny > ( ) ?;
324
335
}
325
336
}
326
337
}
327
338
328
- deserializer. deserialize_map ( OptVisitor :: default ( ) )
339
+ match ( self . pkg , self . var ) {
340
+ ( Some ( pkg) , None ) => Ok ( CheckedOptVisitor {
341
+ opt : Opt :: Pkg ( PkgOpt {
342
+ pkg : pkg. name ,
343
+ components : pkg. components ,
344
+ prerelease_policy : self . prerelease_policy ,
345
+ required_compat : Default :: default ( ) ,
346
+ default : self . default . unwrap_or_default ( ) ,
347
+ value : self . value ,
348
+ } ) ,
349
+ lints : self . lints . clone ( ) ,
350
+ } ) ,
351
+ ( None , Some ( var) ) => Ok ( CheckedOptVisitor {
352
+ opt : Opt :: Var ( VarOpt {
353
+ var,
354
+ choices : self . choices . unwrap_or_default ( ) ,
355
+ inheritance : self . inheritance . unwrap_or_default ( ) ,
356
+ default : self . default . unwrap_or_default ( ) ,
357
+ description : self . description ,
358
+ compat : self . compat ,
359
+ value : self . value ,
360
+ } ) ,
361
+ lints : self . lints . clone ( ) ,
362
+ } ) ,
363
+ ( Some ( _) , Some ( _) ) => Err ( serde:: de:: Error :: custom ( "could not determine option type, it may only contain one of the `pkg` or `var` fields" ) ) ,
364
+ ( None , None ) => Err ( serde:: de:: Error :: custom ( "could not determine option type, it must include either a `pkg` or `var` field" ) ) ,
365
+ }
329
366
}
330
367
}
331
368
0 commit comments