Skip to content

Commit 4452c2f

Browse files
authored
refactor: embed deserialize validation logic in ProgressConfig (#16194)
### What does this PR try to resolve? We have a custom `serde(deserialize_with = "progress_or_string")` to support `term.progress = "never" | "auto" | <progress table>`. It was claimed we added in #8165 [^1] but actually never worked, because `Deserializer::deserialize_option` [^2] never called `visit_str`. This PR remove the custom `progress_or_string` so that the deserialization logic can be baked in the type itself. ### How to test and review this PR? I've tested 1.50 Cargo and `progress = "never"` failed with the same reason as the newly added test. [^1]: #8165 (comment) [^2]: https://github.com/rust-lang/cargo/blob/c369b8c8d85a/src/cargo/util/config/de.rs#L135-L145
2 parents f8f7eb5 + eb0e92f commit 4452c2f

File tree

2 files changed

+53
-62
lines changed

2 files changed

+53
-62
lines changed

src/cargo/util/context/schema.rs

Lines changed: 30 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use std::borrow::Cow;
1414
use std::collections::HashMap;
1515
use std::ffi::OsStr;
16-
use std::fmt;
1716

1817
use serde::Deserialize;
1918
use serde_untagged::UntaggedEnumVisitor;
@@ -342,16 +341,14 @@ pub enum FeatureUnification {
342341
/// color = "auto"
343342
/// progress.when = "auto"
344343
/// ```
345-
#[derive(Deserialize, Default)]
344+
#[derive(Debug, Deserialize, Default)]
346345
#[serde(rename_all = "kebab-case")]
347346
pub struct TermConfig {
348347
pub verbose: Option<bool>,
349348
pub quiet: Option<bool>,
350349
pub color: Option<String>,
351350
pub hyperlinks: Option<bool>,
352351
pub unicode: Option<bool>,
353-
#[serde(default)]
354-
#[serde(deserialize_with = "progress_or_string")]
355352
pub progress: Option<ProgressConfig>,
356353
}
357354

@@ -369,10 +366,8 @@ pub struct TermConfig {
369366
/// [term]
370367
/// progress = { when = "always", width = 80 }
371368
/// ```
372-
#[derive(Debug, Default, Deserialize)]
373-
#[serde(rename_all = "kebab-case")]
369+
#[derive(Debug, Default)]
374370
pub struct ProgressConfig {
375-
#[serde(default)]
376371
pub when: ProgressWhen,
377372
pub width: Option<usize>,
378373
/// Communicate progress status with a terminal
@@ -388,66 +383,39 @@ pub enum ProgressWhen {
388383
Always,
389384
}
390385

391-
fn progress_or_string<'de, D>(deserializer: D) -> Result<Option<ProgressConfig>, D::Error>
392-
where
393-
D: serde::de::Deserializer<'de>,
394-
{
395-
struct ProgressVisitor;
396-
397-
impl<'de> serde::de::Visitor<'de> for ProgressVisitor {
398-
type Value = Option<ProgressConfig>;
399-
400-
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
401-
formatter.write_str("a string (\"auto\" or \"never\") or a table")
402-
}
403-
404-
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
405-
where
406-
E: serde::de::Error,
407-
{
408-
match s {
409-
"auto" => Ok(Some(ProgressConfig {
410-
when: ProgressWhen::Auto,
411-
width: None,
412-
term_integration: None,
413-
})),
414-
"never" => Ok(Some(ProgressConfig {
415-
when: ProgressWhen::Never,
416-
width: None,
417-
term_integration: None,
418-
})),
419-
"always" => Err(E::custom("\"always\" progress requires a `width` key")),
420-
_ => Err(E::unknown_variant(s, &["auto", "never"])),
421-
}
422-
}
423-
424-
fn visit_none<E>(self) -> Result<Self::Value, E>
425-
where
426-
E: serde::de::Error,
427-
{
428-
Ok(None)
386+
// We need this custom deserialization for validadting the rule of
387+
// `when = "always"` requiring a `width` field.
388+
impl<'de> Deserialize<'de> for ProgressConfig {
389+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
390+
where
391+
D: serde::Deserializer<'de>,
392+
{
393+
#[derive(Deserialize)]
394+
#[serde(rename_all = "kebab-case")]
395+
struct ProgressConfigInner {
396+
#[serde(default)]
397+
when: ProgressWhen,
398+
width: Option<usize>,
399+
term_integration: Option<bool>,
429400
}
430401

431-
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
432-
where
433-
D: serde::de::Deserializer<'de>,
402+
let pc = ProgressConfigInner::deserialize(deserializer)?;
403+
if let ProgressConfigInner {
404+
when: ProgressWhen::Always,
405+
width: None,
406+
..
407+
} = pc
434408
{
435-
let pc = ProgressConfig::deserialize(deserializer)?;
436-
if let ProgressConfig {
437-
when: ProgressWhen::Always,
438-
width: None,
439-
..
440-
} = pc
441-
{
442-
return Err(serde::de::Error::custom(
443-
"\"always\" progress requires a `width` key",
444-
));
445-
}
446-
Ok(Some(pc))
409+
return Err(serde::de::Error::custom(
410+
"\"always\" progress requires a `width` key",
411+
));
447412
}
413+
Ok(ProgressConfig {
414+
when: pc.when,
415+
width: pc.width,
416+
term_integration: pc.term_integration,
417+
})
448418
}
449-
450-
deserializer.deserialize_option(ProgressVisitor)
451419
}
452420

453421
#[derive(Debug)]

tests/testsuite/progress.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,26 @@ fn never_progress() {
156156
.with_stderr_does_not_contain("[BUILDING] [..] [..]/4: [..]")
157157
.run();
158158
}
159+
160+
#[cargo_test]
161+
fn plain_string_when_doesnt_work() {
162+
let p = project()
163+
.file(
164+
".cargo/config.toml",
165+
r#"
166+
[term]
167+
progress = "never"
168+
"#,
169+
)
170+
.file("src/lib.rs", "")
171+
.build();
172+
173+
p.cargo("check")
174+
.with_status(101)
175+
.with_stderr_data(str![[r#"
176+
[ERROR] invalid configuration for key `term.progress`
177+
expected a table, but found a string for `term.progress` in [ROOT]/foo/.cargo/config.toml
178+
179+
"#]])
180+
.run();
181+
}

0 commit comments

Comments
 (0)