Skip to main content

cargo/context/
schema.rs

1//! Cargo configuration schemas.
2//!
3//! This module contains types that define the schema for various configuration
4//! sections found in Cargo configuration.
5//!
6//! These types are mostly used by [`GlobalContext::get`](super::GlobalContext::get)
7//! to deserialize configuration values from TOML files, environment variables,
8//! and CLI arguments.
9//!
10//! Schema types here should only contain data and simple accessor methods.
11//! Avoid depending on [`GlobalContext`](super::GlobalContext) directly.
12
13use crate::sources::CRATES_IO_REGISTRY;
14use crate::util::data_structures::HashMap;
15use std::borrow::Cow;
16use std::ffi::OsStr;
17use std::str::FromStr;
18use std::{fmt, hash};
19
20use cargo_credential::Secret;
21use cargo_util_schemas::manifest::ProfileName;
22use serde::Deserialize;
23use serde::Serialize;
24use serde::de;
25use serde_untagged::UntaggedEnumVisitor;
26
27use std::path::Path;
28
29use crate::CargoResult;
30
31use super::OptValue;
32use super::PathAndArgs;
33use super::StringList;
34use super::Value;
35use super::path::ConfigRelativePath;
36
37#[derive(Debug, Default, Deserialize, PartialEq)]
38#[serde(rename_all = "kebab-case")]
39pub struct CargoCacheConfig {
40    /// How often to automatically clean unused cache data.
41    pub auto_clean_frequency: Option<String>,
42    /// Settings for cleaning the global cache.
43    pub global_clean: Option<GlobalCleanConfig>,
44}
45
46/// Cache cleaning settings from the `cache.global-clean` config table.
47///
48/// NOTE: Not all of these options may get stabilized. Some of them are very
49/// low-level details, and may not be something typical users need.
50///
51/// If any of these options are `None`, the built-in default is used.
52#[derive(Debug, Default, Deserialize, PartialEq)]
53#[serde(rename_all = "kebab-case")]
54pub struct GlobalCleanConfig {
55    /// Anything older than this duration will be deleted in the source cache.
56    pub max_src_age: Option<String>,
57    /// Anything older than this duration will be deleted in the compressed crate cache.
58    pub max_crate_age: Option<String>,
59    /// Any index older than this duration will be deleted from the index cache.
60    pub max_index_age: Option<String>,
61    /// Any git checkout older than this duration will be deleted from the checkout cache.
62    pub max_git_co_age: Option<String>,
63    /// Any git clone older than this duration will be deleted from the git cache.
64    pub max_git_db_age: Option<String>,
65}
66
67#[derive(Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub struct CargoNewConfig {
70    #[deprecated = "cargo-new no longer supports adding the authors field"]
71    #[expect(dead_code, reason = "deprecated")]
72    name: Option<String>,
73
74    #[deprecated = "cargo-new no longer supports adding the authors field"]
75    #[expect(dead_code, reason = "deprecated")]
76    email: Option<String>,
77
78    #[serde(rename = "vcs")]
79    pub version_control: Option<VersionControl>,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq)]
83pub enum VersionControl {
84    Git,
85    Hg,
86    Pijul,
87    Fossil,
88    NoVcs,
89}
90
91impl VersionControl {
92    pub const VALUES: &[Self] = &[Self::Git, Self::Hg, Self::Pijul, Self::Fossil, Self::NoVcs];
93
94    pub fn as_str(&self) -> &'static str {
95        match self {
96            VersionControl::Git => "git",
97            VersionControl::Hg => "hg",
98            VersionControl::Pijul => "pijul",
99            VersionControl::Fossil => "fossil",
100            VersionControl::NoVcs => "none",
101        }
102    }
103}
104
105impl FromStr for VersionControl {
106    type Err = anyhow::Error;
107
108    fn from_str(s: &str) -> Result<Self, anyhow::Error> {
109        match s {
110            "git" => Ok(VersionControl::Git),
111            "hg" => Ok(VersionControl::Hg),
112            "pijul" => Ok(VersionControl::Pijul),
113            "fossil" => Ok(VersionControl::Fossil),
114            "none" => Ok(VersionControl::NoVcs),
115            other => anyhow::bail!("unknown vcs specification: `{}`", other),
116        }
117    }
118}
119
120impl<'de> de::Deserialize<'de> for VersionControl {
121    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
122    where
123        D: de::Deserializer<'de>,
124    {
125        let s = String::deserialize(deserializer)?;
126        FromStr::from_str(&s).map_err(de::Error::custom)
127    }
128}
129
130/// Definition of a source in a config file.
131#[derive(Debug, Deserialize)]
132#[serde(rename_all = "kebab-case")]
133pub struct SourceConfigDef {
134    /// Indicates this source should be replaced with another of the given name.
135    pub replace_with: OptValue<String>,
136    /// A directory source.
137    pub directory: Option<ConfigRelativePath>,
138    /// A registry source. Value is a URL.
139    pub registry: OptValue<String>,
140    /// A local registry source.
141    pub local_registry: Option<ConfigRelativePath>,
142    /// A git source. Value is a URL.
143    pub git: OptValue<String>,
144    /// The git branch.
145    pub branch: OptValue<String>,
146    /// The git tag.
147    pub tag: OptValue<String>,
148    /// The git revision.
149    pub rev: OptValue<String>,
150}
151
152/// A map of registry names to URLs where documentations are hosted.
153/// This is for unstable feature [`-Zrustdoc-map`][1].
154///
155/// [1]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#rustdoc-map
156#[derive(serde::Deserialize, Debug)]
157#[serde(default)]
158pub struct RustdocExternMap {
159    #[serde(deserialize_with = "default_crates_io_to_docs_rs")]
160    /// * Key is the registry name in the configuration `[registries.<name>]`.
161    /// * Value is the URL where the documentation is hosted.
162    pub registries: HashMap<String, String>,
163    pub std: Option<RustdocExternMode>,
164}
165
166impl Default for RustdocExternMap {
167    fn default() -> Self {
168        Self {
169            registries: HashMap::from_iter([(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into())]),
170            std: None,
171        }
172    }
173}
174
175const DOCS_RS_URL: &'static str = "https://docs.rs/";
176
177fn default_crates_io_to_docs_rs<'de, D: serde::Deserializer<'de>>(
178    de: D,
179) -> Result<HashMap<String, String>, D::Error> {
180    let mut registries = HashMap::deserialize(de)?;
181    if !registries.contains_key(CRATES_IO_REGISTRY) {
182        registries.insert(CRATES_IO_REGISTRY.into(), DOCS_RS_URL.into());
183    }
184    Ok(registries)
185}
186
187impl hash::Hash for RustdocExternMap {
188    fn hash<H: hash::Hasher>(&self, into: &mut H) {
189        self.std.hash(into);
190        for (key, value) in &self.registries {
191            key.hash(into);
192            value.hash(into);
193        }
194    }
195}
196
197/// Mode used for `std`. This is for unstable feature [`-Zrustdoc-map`][1].
198///
199/// [1]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#rustdoc-map
200#[derive(Debug, Hash)]
201pub enum RustdocExternMode {
202    /// Use a local `file://` URL.
203    Local,
204    /// Use a remote URL to <https://doc.rust-lang.org/> (default).
205    Remote,
206    /// An arbitrary URL.
207    Url(String),
208}
209
210impl From<String> for RustdocExternMode {
211    fn from(s: String) -> RustdocExternMode {
212        match s.as_ref() {
213            "local" => RustdocExternMode::Local,
214            "remote" => RustdocExternMode::Remote,
215            _ => RustdocExternMode::Url(s),
216        }
217    }
218}
219
220impl fmt::Display for RustdocExternMode {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        match self {
223            RustdocExternMode::Local => "local".fmt(f),
224            RustdocExternMode::Remote => "remote".fmt(f),
225            RustdocExternMode::Url(s) => s.fmt(f),
226        }
227    }
228}
229
230impl<'de> serde::de::Deserialize<'de> for RustdocExternMode {
231    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
232    where
233        D: serde::de::Deserializer<'de>,
234    {
235        let s = String::deserialize(deserializer)?;
236        Ok(s.into())
237    }
238}
239
240/// The `[http]` table.
241///
242/// Example configuration:
243///
244/// ```toml
245/// [http]
246/// proxy = "host:port"
247/// timeout = 30
248/// cainfo = "/path/to/ca-bundle.crt"
249/// check-revoke = true
250/// multiplexing = true
251/// ssl-version = "tlsv1.3"
252/// ```
253#[derive(Debug, Default, Deserialize, PartialEq)]
254#[serde(rename_all = "kebab-case")]
255pub struct CargoHttpConfig {
256    pub proxy: Option<String>,
257    pub low_speed_limit: Option<u32>,
258    pub timeout: Option<u64>,
259    pub cainfo: Option<ConfigRelativePath>,
260    pub proxy_cainfo: Option<ConfigRelativePath>,
261    pub check_revoke: Option<bool>,
262    pub user_agent: Option<String>,
263    pub debug: Option<bool>,
264    pub multiplexing: Option<bool>,
265    pub ssl_version: Option<SslVersionConfig>,
266}
267
268/// The `[future-incompat-report]` stable
269///
270/// Example configuration:
271///
272/// ```toml
273/// [future-incompat-report]
274/// frequency = "always"
275/// ```
276#[derive(Debug, Default, Deserialize, PartialEq)]
277#[serde(rename_all = "kebab-case")]
278pub struct CargoFutureIncompatConfig {
279    frequency: Option<CargoFutureIncompatFrequencyConfig>,
280}
281
282#[derive(Debug, Default, Deserialize, PartialEq)]
283#[serde(rename_all = "kebab-case")]
284pub enum CargoFutureIncompatFrequencyConfig {
285    #[default]
286    Always,
287    Never,
288}
289
290impl CargoFutureIncompatConfig {
291    pub fn should_display_message(&self) -> bool {
292        use CargoFutureIncompatFrequencyConfig::*;
293
294        let frequency = self.frequency.as_ref().unwrap_or(&Always);
295        match frequency {
296            Always => true,
297            Never => false,
298        }
299    }
300}
301
302/// Configuration for `ssl-version` in `http` section
303/// There are two ways to configure:
304///
305/// ```text
306/// [http]
307/// ssl-version = "tlsv1.3"
308/// ```
309///
310/// ```text
311/// [http]
312/// ssl-version.min = "tlsv1.2"
313/// ssl-version.max = "tlsv1.3"
314/// ```
315#[derive(Clone, Debug, PartialEq)]
316pub enum SslVersionConfig {
317    Single(String),
318    Range(SslVersionConfigRange),
319}
320
321impl<'de> Deserialize<'de> for SslVersionConfig {
322    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323    where
324        D: serde::Deserializer<'de>,
325    {
326        UntaggedEnumVisitor::new()
327            .string(|single| Ok(SslVersionConfig::Single(single.to_owned())))
328            .map(|map| map.deserialize().map(SslVersionConfig::Range))
329            .deserialize(deserializer)
330    }
331}
332
333#[derive(Clone, Debug, Deserialize, PartialEq)]
334#[serde(rename_all = "kebab-case")]
335pub struct SslVersionConfigRange {
336    pub min: Option<String>,
337    pub max: Option<String>,
338}
339
340/// The `[net]` table.
341///
342/// Example configuration:
343///
344/// ```toml
345/// [net]
346/// retry = 2
347/// offline = false
348/// git-fetch-with-cli = true
349/// ```
350#[derive(Debug, Deserialize)]
351#[serde(rename_all = "kebab-case")]
352pub struct CargoNetConfig {
353    pub retry: Option<u32>,
354    pub offline: Option<bool>,
355    pub git_fetch_with_cli: Option<bool>,
356    pub ssh: Option<CargoSshConfig>,
357}
358
359#[derive(Debug, Deserialize)]
360#[serde(rename_all = "kebab-case")]
361pub struct CargoSshConfig {
362    pub known_hosts: Option<Vec<Value<String>>>,
363}
364
365/// Configuration for `jobs` in `build` section. There are two
366/// ways to configure: An integer or a simple string expression.
367///
368/// ```toml
369/// [build]
370/// jobs = 1
371/// ```
372///
373/// ```toml
374/// [build]
375/// jobs = "default" # Currently only support "default".
376/// ```
377#[derive(Debug, Clone)]
378pub enum JobsConfig {
379    Integer(i32),
380    String(String),
381}
382
383impl<'de> Deserialize<'de> for JobsConfig {
384    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385    where
386        D: serde::Deserializer<'de>,
387    {
388        UntaggedEnumVisitor::new()
389            .i32(|int| Ok(JobsConfig::Integer(int)))
390            .string(|string| Ok(JobsConfig::String(string.to_owned())))
391            .deserialize(deserializer)
392    }
393}
394
395/// The `[build]` table.
396///
397/// Example configuration:
398///
399/// ```toml
400/// [build]
401/// jobs = 4
402/// target = "x86_64-unknown-linux-gnu"
403/// target-dir = "target"
404/// rustflags = ["-C", "link-arg=-fuse-ld=lld"]
405/// incremental = true
406/// ```
407#[derive(Debug, Deserialize)]
408#[serde(rename_all = "kebab-case")]
409pub struct CargoBuildConfig {
410    // deprecated, but preserved for compatibility
411    pub pipelining: Option<bool>,
412    pub dep_info_basedir: Option<ConfigRelativePath>,
413    pub target_dir: Option<ConfigRelativePath>,
414    pub build_dir: Option<ConfigRelativePath>,
415    pub incremental: Option<bool>,
416    pub target: Option<BuildTargetConfig>,
417    pub profile: Option<ProfileName>,
418    pub jobs: Option<JobsConfig>,
419    pub rustflags: Option<StringList>,
420    pub rustdocflags: Option<StringList>,
421    pub rustc_wrapper: Option<ConfigRelativePath>,
422    pub rustc_workspace_wrapper: Option<ConfigRelativePath>,
423    pub rustc: Option<ConfigRelativePath>,
424    pub rustdoc: Option<ConfigRelativePath>,
425    pub artifact_dir: Option<ConfigRelativePath>,
426    pub warnings: Option<WarningHandling>,
427    /// Unstable feature `-Zsbom`.
428    pub sbom: Option<bool>,
429    /// Unstable feature `-Zbuild-analysis`.
430    pub analysis: Option<CargoBuildAnalysis>,
431    /// Unstable feature `-Zchecksum-freshness`.
432    pub fingerprint: Option<FingerprintMethod>,
433}
434
435/// Metrics collection for build analysis.
436#[derive(Debug, Deserialize, Default)]
437#[serde(rename_all = "kebab-case")]
438pub struct CargoBuildAnalysis {
439    pub enabled: bool,
440}
441
442/// Whether warnings should warn, be allowed, or cause an error.
443#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Default)]
444#[serde(rename_all = "kebab-case")]
445pub enum WarningHandling {
446    #[default]
447    /// Output warnings.
448    Warn,
449    /// Allow warnings (do not output them).
450    Allow,
451    /// Error if  warnings are emitted.
452    Deny,
453}
454
455#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)]
456#[serde(rename_all = "kebab-case")]
457pub enum FingerprintMethod {
458    #[default]
459    Mtime,
460    Content,
461}
462
463impl FingerprintMethod {
464    pub fn as_str(&self) -> &'static str {
465        match self {
466            Self::Mtime => "mtime",
467            Self::Content => "content",
468        }
469    }
470}
471
472impl std::fmt::Display for FingerprintMethod {
473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474        self.as_str().fmt(f)
475    }
476}
477
478/// Configuration for `build.target`.
479///
480/// Accepts in the following forms:
481///
482/// ```toml
483/// target = "a"
484/// target = ["a"]
485/// target = ["a", "b"]
486/// ```
487#[derive(Debug, Deserialize)]
488#[serde(transparent)]
489pub struct BuildTargetConfig {
490    inner: Value<BuildTargetConfigInner>,
491}
492
493#[derive(Debug)]
494enum BuildTargetConfigInner {
495    One(String),
496    Many(Vec<String>),
497}
498
499impl<'de> Deserialize<'de> for BuildTargetConfigInner {
500    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
501    where
502        D: serde::Deserializer<'de>,
503    {
504        UntaggedEnumVisitor::new()
505            .string(|one| Ok(BuildTargetConfigInner::One(one.to_owned())))
506            .seq(|many| many.deserialize().map(BuildTargetConfigInner::Many))
507            .deserialize(deserializer)
508    }
509}
510
511impl BuildTargetConfig {
512    /// Gets values of `build.target` as a list of strings.
513    pub fn values(&self, cwd: &Path) -> CargoResult<Vec<String>> {
514        let map = |s: &String| {
515            if s.ends_with(".json") {
516                // Path to a target specification file (in JSON).
517                // <https://doc.rust-lang.org/rustc/targets/custom.html>
518                self.inner
519                    .definition
520                    .root(cwd)
521                    .join(s)
522                    .to_str()
523                    .expect("must be utf-8 in toml")
524                    .to_string()
525            } else {
526                // A string. Probably a target tuple.
527                s.to_string()
528            }
529        };
530        let values = match &self.inner.val {
531            BuildTargetConfigInner::One(s) => vec![map(s)],
532            BuildTargetConfigInner::Many(v) => v.iter().map(map).collect(),
533        };
534        Ok(values)
535    }
536}
537
538/// The `[resolver]` table.
539///
540/// Example configuration:
541///
542/// ```toml
543/// [resolver]
544/// incompatible-rust-versions = "fallback"
545/// incompatible-publish-age = "deny"
546/// feature-unification = "workspace"
547/// lockfile-path = "my/Cargo.lock"
548/// ```
549#[derive(Debug, Deserialize)]
550#[serde(rename_all = "kebab-case")]
551pub struct CargoResolverConfig {
552    pub incompatible_rust_versions: Option<IncompatibleRustVersions>,
553    pub incompatible_publish_age: Option<IncompatiblePublishAge>,
554    pub feature_unification: Option<FeatureUnification>,
555    pub lockfile_path: Option<ConfigRelativePath>,
556}
557
558#[derive(Debug, Deserialize, PartialEq, Eq)]
559#[serde(rename_all = "kebab-case")]
560pub enum IncompatibleRustVersions {
561    Allow,
562    Fallback,
563}
564
565#[derive(Debug, Deserialize, PartialEq, Eq)]
566#[serde(rename_all = "kebab-case")]
567pub enum IncompatiblePublishAge {
568    Allow,
569    Deny,
570}
571
572#[derive(Copy, Clone, Debug, Deserialize)]
573#[serde(rename_all = "kebab-case")]
574pub enum FeatureUnification {
575    Package,
576    Selected,
577    Workspace,
578}
579
580/// The `[term]` table.
581///
582/// Example configuration:
583///
584/// ```toml
585/// [term]
586/// verbose = false
587/// quiet = false
588/// color = "auto"
589/// progress.when = "auto"
590/// ```
591#[derive(Debug, Deserialize, Default)]
592#[serde(rename_all = "kebab-case")]
593pub struct TermConfig {
594    pub verbose: Option<bool>,
595    pub quiet: Option<bool>,
596    pub color: Option<String>,
597    pub hyperlinks: Option<bool>,
598    pub unicode: Option<bool>,
599    pub progress: Option<ProgressConfig>,
600}
601
602/// The `term.progress` configuration.
603///
604/// Example configuration:
605///
606/// ```toml
607/// [term]
608/// progress.when = "never" # or "auto"
609/// ```
610///
611/// ```toml
612/// # `when = "always"` requires a `width` field
613/// [term]
614/// progress = { when = "always", width = 80 }
615/// ```
616#[derive(Debug, Default)]
617pub struct ProgressConfig {
618    pub when: ProgressWhen,
619    pub width: Option<usize>,
620    /// Communicate progress status with a terminal
621    pub term_integration: Option<bool>,
622}
623
624#[derive(Debug, Default, Deserialize)]
625#[serde(rename_all = "kebab-case")]
626pub enum ProgressWhen {
627    #[default]
628    Auto,
629    Never,
630    Always,
631}
632
633// We need this custom deserialization for validadting the rule of
634// `when = "always"` requiring a `width` field.
635impl<'de> Deserialize<'de> for ProgressConfig {
636    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
637    where
638        D: serde::Deserializer<'de>,
639    {
640        #[derive(Deserialize)]
641        #[serde(rename_all = "kebab-case")]
642        struct ProgressConfigInner {
643            #[serde(default)]
644            when: ProgressWhen,
645            width: Option<usize>,
646            term_integration: Option<bool>,
647        }
648
649        let pc = ProgressConfigInner::deserialize(deserializer)?;
650        if let ProgressConfigInner {
651            when: ProgressWhen::Always,
652            width: None,
653            ..
654        } = pc
655        {
656            return Err(serde::de::Error::custom(
657                "\"always\" progress requires a `width` key",
658            ));
659        }
660        Ok(ProgressConfig {
661            when: pc.when,
662            width: pc.width,
663            term_integration: pc.term_integration,
664        })
665    }
666}
667
668#[derive(Debug)]
669enum EnvConfigValueInner {
670    Simple(String),
671    WithOptions {
672        value: ConfigRelativePath,
673        force: bool,
674        relative: bool,
675    },
676}
677
678impl<'de> Deserialize<'de> for EnvConfigValueInner {
679    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
680    where
681        D: serde::Deserializer<'de>,
682    {
683        #[derive(Deserialize)]
684        struct WithOptions {
685            value: ConfigRelativePath,
686            #[serde(default)]
687            force: bool,
688            #[serde(default)]
689            relative: bool,
690        }
691
692        UntaggedEnumVisitor::new()
693            .string(|simple| Ok(EnvConfigValueInner::Simple(simple.to_owned())))
694            .map(|map| {
695                let with_options: WithOptions = map.deserialize()?;
696                Ok(EnvConfigValueInner::WithOptions {
697                    value: with_options.value,
698                    force: with_options.force,
699                    relative: with_options.relative,
700                })
701            })
702            .deserialize(deserializer)
703    }
704}
705
706/// Configuration value for environment variables in `[env]` section.
707///
708/// Supports two formats: simple string and with options.
709///
710/// ```toml
711/// [env]
712/// FOO = "value"
713/// ```
714///
715/// ```toml
716/// [env]
717/// BAR = { value = "relative/path", relative = true }
718/// BAZ = { value = "override", force = true }
719/// ```
720#[derive(Debug, Deserialize)]
721#[serde(transparent)]
722pub struct EnvConfigValue {
723    inner: EnvConfigValueInner,
724}
725
726impl EnvConfigValue {
727    /// Whether this value should override existing environment variables.
728    pub fn is_force(&self) -> bool {
729        match self.inner {
730            EnvConfigValueInner::Simple(_) => false,
731            EnvConfigValueInner::WithOptions { force, .. } => force,
732        }
733    }
734
735    /// Resolves the environment variable value.
736    ///
737    /// If `relative = true`,
738    /// the value is interpreted as a [`ConfigRelativePath`]-like path.
739    pub fn resolve<'a>(&'a self, cwd: &Path) -> Cow<'a, OsStr> {
740        match self.inner {
741            EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())),
742            EnvConfigValueInner::WithOptions {
743                ref value,
744                relative,
745                ..
746            } => {
747                if relative {
748                    let p = value.value().definition.root(cwd).join(value.raw_value());
749                    Cow::Owned(p.into_os_string())
750                } else {
751                    Cow::Borrowed(OsStr::new(value.raw_value()))
752                }
753            }
754        }
755    }
756}
757
758pub type EnvConfig = HashMap<String, EnvConfigValue>;
759
760/// `[registries.NAME]` tables.
761///
762/// The values here should be kept in sync with `GlobalRegistryConfig`
763#[derive(Deserialize, Clone, Debug)]
764#[serde(rename_all = "kebab-case")]
765pub struct RegistryConfig {
766    pub index: Option<String>,
767    pub token: OptValue<Secret<String>>,
768    pub credential_provider: Option<PathAndArgs>,
769    /// Minimum publish age threshold for RFC 3923
770    pub min_publish_age: Option<String>,
771    #[serde(rename = "protocol")]
772    _protocol: Option<String>,
773}
774
775/// The `[registry]` table, which has more keys than the `[registries.NAME]` tables.
776///
777/// Note: nesting `RegistryConfig` inside this struct and using `serde(flatten)` *should* work
778/// but fails with "invalid type: sequence, expected a value" when attempting to deserialize.
779#[derive(Deserialize)]
780#[serde(rename_all = "kebab-case")]
781pub struct GlobalRegistryConfig {
782    pub index: Option<String>,
783    pub token: OptValue<Secret<String>>,
784    pub credential_provider: Option<PathAndArgs>,
785    /// Global default Minimum publish age threshold for RFC 3923
786    pub global_min_publish_age: Option<String>,
787    #[serde(rename = "default")]
788    _default: Option<String>,
789    #[serde(rename = "global-credential-providers")]
790    _global_credential_providers: Option<Vec<String>>,
791}
792
793impl GlobalRegistryConfig {
794    pub fn to_registry_config(self) -> RegistryConfig {
795        RegistryConfig {
796            index: self.index,
797            token: self.token,
798            credential_provider: self.credential_provider,
799            // `min-publish-age` is per-registry config only.
800            min_publish_age: None,
801            _protocol: None,
802        }
803    }
804}