6
6
//! Since `cargo config get` is not stable as of Rust 1.61, nextest must do its own config file
7
7
//! search.
8
8
9
- use crate :: errors:: { CargoConfigSearchError , CargoConfigsConstructError , TargetTripleError } ;
9
+ use crate :: errors:: {
10
+ CargoConfigSearchError , CargoConfigsConstructError , InvalidCargoCliConfigReason ,
11
+ TargetTripleError ,
12
+ } ;
10
13
use camino:: { Utf8Path , Utf8PathBuf } ;
11
14
use once_cell:: sync:: OnceCell ;
12
15
use serde:: Deserialize ;
13
16
use std:: { collections:: BTreeMap , fmt} ;
17
+ use toml_edit:: Item ;
14
18
15
19
/// Represents a target triple that's being cross-compiled against.
16
20
#[ derive( Clone , Debug , PartialEq , Eq ) ]
@@ -221,17 +225,103 @@ fn parse_cli_configs(
221
225
. map ( |config_str| {
222
226
// Each cargo config is expected to be a valid TOML file.
223
227
let config_str = config_str. as_ref ( ) ;
224
- let config = toml_edit:: easy:: from_str ( config_str) . map_err ( |error| {
225
- CargoConfigsConstructError :: CliConfigParseError {
226
- config_str : config_str. to_owned ( ) ,
227
- error,
228
- }
229
- } ) ?;
228
+ let config = parse_cli_config ( config_str) ?;
230
229
Ok ( ( CargoConfigSource :: CliOption , config) )
231
230
} )
232
231
. collect ( )
233
232
}
234
233
234
+ fn parse_cli_config ( config_str : & str ) -> Result < CargoConfig , CargoConfigsConstructError > {
235
+ // This implementation is copied over from https://github.com/rust-lang/cargo/pull/10176.
236
+
237
+ // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
238
+ // expressions followed by a value that's not an "inline table"
239
+ // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
240
+ // parse the value as a toml_edit::Document, and check that the (single)
241
+ // inner-most table is set via dotted keys.
242
+ let doc: toml_edit:: Document =
243
+ config_str
244
+ . parse ( )
245
+ . map_err ( |error| CargoConfigsConstructError :: CliConfigParseError {
246
+ config_str : config_str. to_owned ( ) ,
247
+ error,
248
+ } ) ?;
249
+
250
+ fn non_empty_decor ( d : & toml_edit:: Decor ) -> bool {
251
+ d. prefix ( ) . map_or ( false , |p| !p. trim ( ) . is_empty ( ) )
252
+ || d. suffix ( ) . map_or ( false , |s| !s. trim ( ) . is_empty ( ) )
253
+ }
254
+
255
+ let ok = {
256
+ let mut got_to_value = false ;
257
+ let mut table = doc. as_table ( ) ;
258
+ let mut is_root = true ;
259
+ while table. is_dotted ( ) || is_root {
260
+ is_root = false ;
261
+ if table. len ( ) != 1 {
262
+ break ;
263
+ }
264
+ let ( k, n) = table. iter ( ) . next ( ) . expect ( "len() == 1 above" ) ;
265
+ match n {
266
+ Item :: Table ( nt) => {
267
+ if table. key_decor ( k) . map_or ( false , non_empty_decor)
268
+ || non_empty_decor ( nt. decor ( ) )
269
+ {
270
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
271
+ config_str : config_str. to_owned ( ) ,
272
+ reason : InvalidCargoCliConfigReason :: IncludesNonWhitespaceDecoration ,
273
+ } ) ?;
274
+ }
275
+ table = nt;
276
+ }
277
+ Item :: Value ( v) if v. is_inline_table ( ) => {
278
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
279
+ config_str : config_str. to_owned ( ) ,
280
+ reason : InvalidCargoCliConfigReason :: SetsValueToInlineTable ,
281
+ } ) ?;
282
+ }
283
+ Item :: Value ( v) => {
284
+ if non_empty_decor ( v. decor ( ) ) {
285
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
286
+ config_str : config_str. to_owned ( ) ,
287
+ reason : InvalidCargoCliConfigReason :: IncludesNonWhitespaceDecoration ,
288
+ } ) ?;
289
+ }
290
+ got_to_value = true ;
291
+ break ;
292
+ }
293
+ Item :: ArrayOfTables ( _) => {
294
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
295
+ config_str : config_str. to_owned ( ) ,
296
+ reason : InvalidCargoCliConfigReason :: SetsValueToArrayOfTables ,
297
+ } ) ?;
298
+ }
299
+ Item :: None => {
300
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
301
+ config_str : config_str. to_owned ( ) ,
302
+ reason : InvalidCargoCliConfigReason :: DoesntProvideValue ,
303
+ } ) ?;
304
+ }
305
+ }
306
+ }
307
+ got_to_value
308
+ } ;
309
+ if !ok {
310
+ return Err ( CargoConfigsConstructError :: InvalidCliConfig {
311
+ config_str : config_str. to_owned ( ) ,
312
+ reason : InvalidCargoCliConfigReason :: NotDottedKv ,
313
+ } ) ?;
314
+ }
315
+
316
+ let cargo_config: CargoConfig = toml_edit:: easy:: from_document ( doc) . map_err ( |error| {
317
+ CargoConfigsConstructError :: CliConfigDeError {
318
+ config_str : config_str. to_owned ( ) ,
319
+ error,
320
+ }
321
+ } ) ?;
322
+ Ok ( cargo_config)
323
+ }
324
+
235
325
fn discover_impl (
236
326
start_search_at : & Utf8Path ,
237
327
terminate_search_at : Option < & Utf8Path > ,
@@ -342,7 +432,7 @@ pub(crate) struct CargoConfigRunner {
342
432
pub ( crate ) runner : Option < Runner > ,
343
433
}
344
434
345
- #[ derive( Clone , Deserialize , Debug ) ]
435
+ #[ derive( Clone , Deserialize , Debug , Eq , PartialEq ) ]
346
436
#[ serde( untagged) ]
347
437
pub ( crate ) enum Runner {
348
438
Simple ( String ) ,
@@ -355,6 +445,74 @@ mod tests {
355
445
use camino:: Utf8Path ;
356
446
use color_eyre:: eyre:: { Context , Result } ;
357
447
use tempfile:: TempDir ;
448
+ use test_case:: test_case;
449
+
450
+ #[ test]
451
+ fn test_cli_kv_accepted ( ) {
452
+ // These dotted key expressions should all be fine.
453
+ let config = parse_cli_config ( "build.target=\" aarch64-unknown-linux-gnu\" " )
454
+ . expect ( "dotted config should parse correctly" ) ;
455
+ assert_eq ! (
456
+ config. build. target. as_deref( ) ,
457
+ Some ( "aarch64-unknown-linux-gnu" )
458
+ ) ;
459
+
460
+ let config = parse_cli_config ( " target.\" aarch64-unknown-linux-gnu\" .runner = 'test' " )
461
+ . expect ( "dotted config should parse correctly" ) ;
462
+ assert_eq ! (
463
+ config. target. as_ref( ) . unwrap( ) [ "aarch64-unknown-linux-gnu" ] . runner,
464
+ Some ( Runner :: Simple ( "test" . to_owned( ) ) )
465
+ ) ;
466
+
467
+ // But anything that's not a dotted key expression should be disallowed.
468
+ let _ = parse_cli_config ( "[a] foo=true" ) . unwrap_err ( ) ;
469
+ let _ = parse_cli_config ( "a = true\n b = true" ) . unwrap_err ( ) ;
470
+
471
+ // We also disallow overwriting with tables since it makes merging unclear.
472
+ let _ = parse_cli_config ( "a = { first = true, second = false }" ) . unwrap_err ( ) ;
473
+ let _ = parse_cli_config ( "a = { first = true }" ) . unwrap_err ( ) ;
474
+ }
475
+
476
+ #[ test_case(
477
+ "" ,
478
+ InvalidCargoCliConfigReason :: NotDottedKv
479
+
480
+ ; "empty input" ) ]
481
+ #[ test_case(
482
+ "a.b={c = \" d\" }" ,
483
+ InvalidCargoCliConfigReason :: SetsValueToInlineTable
484
+
485
+ ; "no inline table value" ) ]
486
+ #[ test_case(
487
+ "[[a.b]]\n c = \" d\" " ,
488
+ InvalidCargoCliConfigReason :: NotDottedKv
489
+
490
+ ; "no array of tables" ) ]
491
+ #[ test_case(
492
+ "a.b = \" c\" # exactly" ,
493
+ InvalidCargoCliConfigReason :: IncludesNonWhitespaceDecoration
494
+
495
+ ; "no comments after" ) ]
496
+ #[ test_case(
497
+ "# exactly\n a.b = \" c\" " ,
498
+ InvalidCargoCliConfigReason :: IncludesNonWhitespaceDecoration
499
+
500
+ ; "no comments before" ) ]
501
+ fn test_invalid_cli_config_reason ( arg : & str , expected_reason : InvalidCargoCliConfigReason ) {
502
+ // Disallow inline tables
503
+ let err = parse_cli_config ( arg) . unwrap_err ( ) ;
504
+ let actual_reason = match err {
505
+ CargoConfigsConstructError :: InvalidCliConfig { reason, .. } => reason,
506
+ other => panic ! (
507
+ "expected input {arg} to fail with InvalidCliConfig, actual failure: {other}"
508
+ ) ,
509
+ } ;
510
+
511
+ assert_eq ! (
512
+ expected_reason, actual_reason,
513
+ "expected reason for failure doesn't match actual reason"
514
+ ) ;
515
+ }
358
516
359
517
#[ test]
360
518
fn test_find_target_triple ( ) {
0 commit comments