Skip to main content

cargo_util_schemas/manifest/
mod.rs

1//! `Cargo.toml` / Manifest schema definition
2//!
3//! ## Style
4//!
5//! - Fields duplicated for an alias will have an accessor with the primary field's name
6//! - Keys that exist for bookkeeping but don't correspond to the schema have a `_` prefix
7
8use std::collections::BTreeMap;
9use std::collections::BTreeSet;
10use std::fmt::{self, Display, Write};
11use std::path::PathBuf;
12use std::str;
13
14use serde::de::{self, IntoDeserializer as _, Unexpected};
15use serde::ser;
16use serde::{Deserialize, Serialize};
17use serde_untagged::UntaggedEnumVisitor;
18
19use crate::core::PackageIdSpec;
20use crate::restricted_names;
21
22mod rust_version;
23
24pub use crate::restricted_names::NameValidationError;
25pub use rust_version::RustVersion;
26pub use rust_version::RustVersionError;
27
28#[cfg(feature = "unstable-schema")]
29use crate::schema::TomlValueWrapper;
30
31/// This type is used to deserialize `Cargo.toml` files.
32#[derive(Default, Clone, Debug, Deserialize, Serialize)]
33#[serde(rename_all = "kebab-case")]
34#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
35pub struct TomlManifest {
36    pub cargo_features: Option<Vec<String>>,
37
38    // Update `requires_package` when adding new package-specific fields
39    pub package: Option<Box<TomlPackage>>,
40    pub project: Option<Box<TomlPackage>>,
41    pub badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
42    pub features: Option<BTreeMap<FeatureName, FeatureDefinition>>,
43    pub lib: Option<TomlLibTarget>,
44    pub bin: Option<Vec<TomlBinTarget>>,
45    pub example: Option<Vec<TomlExampleTarget>>,
46    pub test: Option<Vec<TomlTestTarget>>,
47    pub bench: Option<Vec<TomlTestTarget>>,
48    pub dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
49    pub dev_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
50    #[serde(rename = "dev_dependencies")]
51    pub dev_dependencies2: Option<BTreeMap<PackageName, InheritableDependency>>,
52    pub build_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
53    #[serde(rename = "build_dependencies")]
54    pub build_dependencies2: Option<BTreeMap<PackageName, InheritableDependency>>,
55    pub target: Option<BTreeMap<String, TomlPlatform>>,
56    pub lints: Option<InheritableLints>,
57    pub hints: Option<Hints>,
58
59    pub workspace: Option<TomlWorkspace>,
60    pub profile: Option<TomlProfiles>,
61    pub patch: Option<BTreeMap<String, BTreeMap<PackageName, TomlDependency>>>,
62    pub replace: Option<BTreeMap<String, TomlDependency>>,
63
64    /// Report unused keys (see also nested `_unused_keys`)
65    /// Note: this is populated by the caller, rather than automatically
66    #[serde(skip)]
67    pub _unused_keys: BTreeSet<String>,
68}
69
70impl TomlManifest {
71    pub fn requires_package(&self) -> impl Iterator<Item = &'static str> {
72        [
73            self.badges.as_ref().map(|_| "badges"),
74            self.features.as_ref().map(|_| "features"),
75            self.lib.as_ref().map(|_| "lib"),
76            self.bin.as_ref().map(|_| "bin"),
77            self.example.as_ref().map(|_| "example"),
78            self.test.as_ref().map(|_| "test"),
79            self.bench.as_ref().map(|_| "bench"),
80            self.dependencies.as_ref().map(|_| "dependencies"),
81            self.dev_dependencies().as_ref().map(|_| "dev-dependencies"),
82            self.build_dependencies()
83                .as_ref()
84                .map(|_| "build-dependencies"),
85            self.target.as_ref().map(|_| "target"),
86            self.lints.as_ref().map(|_| "lints"),
87            self.hints.as_ref().map(|_| "hints"),
88        ]
89        .into_iter()
90        .flatten()
91    }
92
93    pub fn has_profiles(&self) -> bool {
94        self.profile.is_some()
95    }
96
97    pub fn package(&self) -> Option<&Box<TomlPackage>> {
98        self.package.as_ref().or(self.project.as_ref())
99    }
100
101    pub fn dev_dependencies(&self) -> Option<&BTreeMap<PackageName, InheritableDependency>> {
102        self.dev_dependencies
103            .as_ref()
104            .or(self.dev_dependencies2.as_ref())
105    }
106
107    pub fn build_dependencies(&self) -> Option<&BTreeMap<PackageName, InheritableDependency>> {
108        self.build_dependencies
109            .as_ref()
110            .or(self.build_dependencies2.as_ref())
111    }
112
113    pub fn features(&self) -> Option<&BTreeMap<FeatureName, FeatureDefinition>> {
114        self.features.as_ref()
115    }
116
117    pub fn normalized_lints(&self) -> Result<Option<&TomlLints>, UnresolvedError> {
118        self.lints.as_ref().map(|l| l.normalized()).transpose()
119    }
120}
121
122#[derive(Debug, Default, Deserialize, Serialize, Clone)]
123#[serde(rename_all = "kebab-case")]
124#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
125pub struct TomlWorkspace {
126    pub members: Option<Vec<String>>,
127    pub exclude: Option<Vec<String>>,
128    pub default_members: Option<Vec<String>>,
129    pub resolver: Option<String>,
130
131    #[cfg_attr(
132        feature = "unstable-schema",
133        schemars(with = "Option<TomlValueWrapper>")
134    )]
135    pub metadata: Option<toml::Value>,
136
137    // Properties that can be inherited by members.
138    pub package: Option<InheritablePackage>,
139    pub dependencies: Option<BTreeMap<PackageName, TomlDependency>>,
140    pub lints: Option<TomlLints>,
141}
142
143/// A group of fields that are inheritable by members of the workspace
144#[derive(Clone, Debug, Default, Deserialize, Serialize)]
145#[serde(rename_all = "kebab-case")]
146#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
147pub struct InheritablePackage {
148    pub version: Option<semver::Version>,
149    pub authors: Option<Vec<String>>,
150    pub description: Option<String>,
151    pub homepage: Option<String>,
152    pub documentation: Option<String>,
153    pub readme: Option<StringOrBool>,
154    pub keywords: Option<Vec<String>>,
155    pub categories: Option<Vec<String>>,
156    pub license: Option<String>,
157    pub license_file: Option<String>,
158    pub repository: Option<String>,
159    pub publish: Option<VecStringOrBool>,
160    pub edition: Option<String>,
161    pub badges: Option<BTreeMap<String, BTreeMap<String, String>>>,
162    pub exclude: Option<Vec<String>>,
163    pub include: Option<Vec<String>>,
164    pub rust_version: Option<RustVersion>,
165}
166
167/// Represents the `package`/`project` sections of a `Cargo.toml`.
168///
169/// Note that the order of the fields matters, since this is the order they
170/// are serialized to a TOML file. For example, you cannot have values after
171/// the field `metadata`, since it is a table and values cannot appear after
172/// tables.
173#[derive(Deserialize, Serialize, Clone, Debug, Default)]
174#[serde(rename_all = "kebab-case")]
175#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
176pub struct TomlPackage {
177    pub edition: Option<InheritableString>,
178    pub rust_version: Option<InheritableRustVersion>,
179    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
180    pub name: Option<PackageName>,
181    pub version: Option<InheritableSemverVersion>,
182    pub authors: Option<InheritableVecString>,
183    pub build: Option<TomlPackageBuild>,
184    pub metabuild: Option<StringOrVec>,
185    pub default_target: Option<String>,
186    pub forced_target: Option<String>,
187    pub links: Option<String>,
188    pub exclude: Option<InheritableVecString>,
189    pub include: Option<InheritableVecString>,
190    pub publish: Option<InheritableVecStringOrBool>,
191    pub workspace: Option<String>,
192    pub im_a_teapot: Option<bool>,
193    pub autolib: Option<bool>,
194    pub autobins: Option<bool>,
195    pub autoexamples: Option<bool>,
196    pub autotests: Option<bool>,
197    pub autobenches: Option<bool>,
198    pub default_run: Option<String>,
199
200    // Package metadata.
201    pub description: Option<InheritableString>,
202    pub homepage: Option<InheritableString>,
203    pub documentation: Option<InheritableString>,
204    pub readme: Option<InheritableStringOrBool>,
205    pub keywords: Option<InheritableVecString>,
206    pub categories: Option<InheritableVecString>,
207    pub license: Option<InheritableString>,
208    pub license_file: Option<InheritableString>,
209    pub repository: Option<InheritableString>,
210    pub resolver: Option<String>,
211
212    #[cfg_attr(
213        feature = "unstable-schema",
214        schemars(with = "Option<TomlValueWrapper>")
215    )]
216    pub metadata: Option<toml::Value>,
217
218    /// Provide a helpful error message for a common user error.
219    #[serde(rename = "cargo-features", skip_serializing)]
220    #[cfg_attr(feature = "unstable-schema", schemars(skip))]
221    pub _invalid_cargo_features: Option<InvalidCargoFeatures>,
222}
223
224impl TomlPackage {
225    pub fn new(name: PackageName) -> Self {
226        Self {
227            name: Some(name),
228            ..Default::default()
229        }
230    }
231
232    pub fn normalized_name(&self) -> Result<&PackageName, UnresolvedError> {
233        self.name.as_ref().ok_or(UnresolvedError)
234    }
235
236    pub fn normalized_edition(&self) -> Result<Option<&String>, UnresolvedError> {
237        self.edition.as_ref().map(|v| v.normalized()).transpose()
238    }
239
240    pub fn normalized_rust_version(&self) -> Result<Option<&RustVersion>, UnresolvedError> {
241        self.rust_version
242            .as_ref()
243            .map(|v| v.normalized())
244            .transpose()
245    }
246
247    pub fn normalized_version(&self) -> Result<Option<&semver::Version>, UnresolvedError> {
248        self.version.as_ref().map(|v| v.normalized()).transpose()
249    }
250
251    pub fn normalized_authors(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
252        self.authors.as_ref().map(|v| v.normalized()).transpose()
253    }
254
255    pub fn normalized_build(&self) -> Result<Option<&[String]>, UnresolvedError> {
256        let build = self.build.as_ref().ok_or(UnresolvedError)?;
257        match build {
258            TomlPackageBuild::Auto(false) => Ok(None),
259            TomlPackageBuild::Auto(true) => Err(UnresolvedError),
260            TomlPackageBuild::SingleScript(value) => Ok(Some(std::slice::from_ref(value))),
261            TomlPackageBuild::MultipleScript(scripts) => Ok(Some(scripts)),
262        }
263    }
264
265    pub fn normalized_exclude(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
266        self.exclude.as_ref().map(|v| v.normalized()).transpose()
267    }
268
269    pub fn normalized_include(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
270        self.include.as_ref().map(|v| v.normalized()).transpose()
271    }
272
273    pub fn normalized_publish(&self) -> Result<Option<&VecStringOrBool>, UnresolvedError> {
274        self.publish.as_ref().map(|v| v.normalized()).transpose()
275    }
276
277    pub fn normalized_description(&self) -> Result<Option<&String>, UnresolvedError> {
278        self.description
279            .as_ref()
280            .map(|v| v.normalized())
281            .transpose()
282    }
283
284    pub fn normalized_homepage(&self) -> Result<Option<&String>, UnresolvedError> {
285        self.homepage.as_ref().map(|v| v.normalized()).transpose()
286    }
287
288    pub fn normalized_documentation(&self) -> Result<Option<&String>, UnresolvedError> {
289        self.documentation
290            .as_ref()
291            .map(|v| v.normalized())
292            .transpose()
293    }
294
295    pub fn normalized_readme(&self) -> Result<Option<&String>, UnresolvedError> {
296        let readme = self.readme.as_ref().ok_or(UnresolvedError)?;
297        readme.normalized().and_then(|sb| match sb {
298            StringOrBool::Bool(false) => Ok(None),
299            StringOrBool::Bool(true) => Err(UnresolvedError),
300            StringOrBool::String(value) => Ok(Some(value)),
301        })
302    }
303
304    pub fn normalized_keywords(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
305        self.keywords.as_ref().map(|v| v.normalized()).transpose()
306    }
307
308    pub fn normalized_categories(&self) -> Result<Option<&Vec<String>>, UnresolvedError> {
309        self.categories.as_ref().map(|v| v.normalized()).transpose()
310    }
311
312    pub fn normalized_license(&self) -> Result<Option<&String>, UnresolvedError> {
313        self.license.as_ref().map(|v| v.normalized()).transpose()
314    }
315
316    pub fn normalized_license_file(&self) -> Result<Option<&String>, UnresolvedError> {
317        self.license_file
318            .as_ref()
319            .map(|v| v.normalized())
320            .transpose()
321    }
322
323    pub fn normalized_repository(&self) -> Result<Option<&String>, UnresolvedError> {
324        self.repository.as_ref().map(|v| v.normalized()).transpose()
325    }
326}
327
328/// An enum that allows for inheriting keys from a workspace in a Cargo.toml.
329#[derive(Serialize, Copy, Clone, Debug)]
330#[serde(untagged)]
331#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
332pub enum InheritableField<T> {
333    /// The type that is used when not inheriting from a workspace.
334    Value(T),
335    /// The type when inheriting from a workspace.
336    Inherit(TomlInheritedField),
337}
338
339impl<T> InheritableField<T> {
340    pub fn normalized(&self) -> Result<&T, UnresolvedError> {
341        self.as_value().ok_or(UnresolvedError)
342    }
343
344    pub fn as_value(&self) -> Option<&T> {
345        match self {
346            InheritableField::Inherit(_) => None,
347            InheritableField::Value(defined) => Some(defined),
348        }
349    }
350
351    pub fn into_value(self) -> Option<T> {
352        match self {
353            Self::Inherit(_) => None,
354            Self::Value(defined) => Some(defined),
355        }
356    }
357
358    pub fn is_inherited(&self) -> bool {
359        matches!(self, Self::Inherit(_))
360    }
361}
362
363//. This already has a `Deserialize` impl from version_trim_whitespace
364pub type InheritableSemverVersion = InheritableField<semver::Version>;
365impl<'de> de::Deserialize<'de> for InheritableSemverVersion {
366    fn deserialize<D>(d: D) -> Result<Self, D::Error>
367    where
368        D: de::Deserializer<'de>,
369    {
370        UntaggedEnumVisitor::new()
371            .expecting("SemVer version")
372            .string(
373                |value| match value.trim().parse().map_err(de::Error::custom) {
374                    Ok(parsed) => Ok(InheritableField::Value(parsed)),
375                    Err(e) => Err(e),
376                },
377            )
378            .map(|value| value.deserialize().map(InheritableField::Inherit))
379            .deserialize(d)
380    }
381}
382
383pub type InheritableString = InheritableField<String>;
384impl<'de> de::Deserialize<'de> for InheritableString {
385    fn deserialize<D>(d: D) -> Result<Self, D::Error>
386    where
387        D: de::Deserializer<'de>,
388    {
389        struct Visitor;
390
391        impl<'de> de::Visitor<'de> for Visitor {
392            type Value = InheritableString;
393
394            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
395                f.write_str("a string or workspace")
396            }
397
398            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
399            where
400                E: de::Error,
401            {
402                Ok(InheritableString::Value(value))
403            }
404
405            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
406            where
407                E: de::Error,
408            {
409                self.visit_string(value.to_owned())
410            }
411
412            fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
413            where
414                V: de::MapAccess<'de>,
415            {
416                let mvd = de::value::MapAccessDeserializer::new(map);
417                TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
418            }
419        }
420
421        d.deserialize_any(Visitor)
422    }
423}
424
425pub type InheritableRustVersion = InheritableField<RustVersion>;
426impl<'de> de::Deserialize<'de> for InheritableRustVersion {
427    fn deserialize<D>(d: D) -> Result<Self, D::Error>
428    where
429        D: de::Deserializer<'de>,
430    {
431        struct Visitor;
432
433        impl<'de> de::Visitor<'de> for Visitor {
434            type Value = InheritableRustVersion;
435
436            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
437                f.write_str("a semver or workspace")
438            }
439
440            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
441            where
442                E: de::Error,
443            {
444                let value = value.parse::<RustVersion>().map_err(|e| E::custom(e))?;
445                Ok(InheritableRustVersion::Value(value))
446            }
447
448            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
449            where
450                E: de::Error,
451            {
452                self.visit_string(value.to_owned())
453            }
454
455            fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
456            where
457                V: de::MapAccess<'de>,
458            {
459                let mvd = de::value::MapAccessDeserializer::new(map);
460                TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
461            }
462        }
463
464        d.deserialize_any(Visitor)
465    }
466}
467
468pub type InheritableVecString = InheritableField<Vec<String>>;
469impl<'de> de::Deserialize<'de> for InheritableVecString {
470    fn deserialize<D>(d: D) -> Result<Self, D::Error>
471    where
472        D: de::Deserializer<'de>,
473    {
474        struct Visitor;
475
476        impl<'de> de::Visitor<'de> for Visitor {
477            type Value = InheritableVecString;
478
479            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
480                f.write_str("a vector of strings or workspace")
481            }
482            fn visit_seq<A>(self, v: A) -> Result<Self::Value, A::Error>
483            where
484                A: de::SeqAccess<'de>,
485            {
486                let seq = de::value::SeqAccessDeserializer::new(v);
487                Vec::deserialize(seq).map(InheritableField::Value)
488            }
489
490            fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
491            where
492                V: de::MapAccess<'de>,
493            {
494                let mvd = de::value::MapAccessDeserializer::new(map);
495                TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
496            }
497        }
498
499        d.deserialize_any(Visitor)
500    }
501}
502
503pub type InheritableStringOrBool = InheritableField<StringOrBool>;
504impl<'de> de::Deserialize<'de> for InheritableStringOrBool {
505    fn deserialize<D>(d: D) -> Result<Self, D::Error>
506    where
507        D: de::Deserializer<'de>,
508    {
509        struct Visitor;
510
511        impl<'de> de::Visitor<'de> for Visitor {
512            type Value = InheritableStringOrBool;
513
514            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
515                f.write_str("a string, a bool, or workspace")
516            }
517
518            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
519            where
520                E: de::Error,
521            {
522                let b = de::value::BoolDeserializer::new(v);
523                StringOrBool::deserialize(b).map(InheritableField::Value)
524            }
525
526            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
527            where
528                E: de::Error,
529            {
530                let string = de::value::StringDeserializer::new(v);
531                StringOrBool::deserialize(string).map(InheritableField::Value)
532            }
533
534            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
535            where
536                E: de::Error,
537            {
538                self.visit_string(value.to_owned())
539            }
540
541            fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
542            where
543                V: de::MapAccess<'de>,
544            {
545                let mvd = de::value::MapAccessDeserializer::new(map);
546                TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
547            }
548        }
549
550        d.deserialize_any(Visitor)
551    }
552}
553
554pub type InheritableVecStringOrBool = InheritableField<VecStringOrBool>;
555impl<'de> de::Deserialize<'de> for InheritableVecStringOrBool {
556    fn deserialize<D>(d: D) -> Result<Self, D::Error>
557    where
558        D: de::Deserializer<'de>,
559    {
560        struct Visitor;
561
562        impl<'de> de::Visitor<'de> for Visitor {
563            type Value = InheritableVecStringOrBool;
564
565            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
566                f.write_str("a boolean, a vector of strings, or workspace")
567            }
568
569            fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
570            where
571                E: de::Error,
572            {
573                let b = de::value::BoolDeserializer::new(v);
574                VecStringOrBool::deserialize(b).map(InheritableField::Value)
575            }
576
577            fn visit_seq<A>(self, v: A) -> Result<Self::Value, A::Error>
578            where
579                A: de::SeqAccess<'de>,
580            {
581                let seq = de::value::SeqAccessDeserializer::new(v);
582                VecStringOrBool::deserialize(seq).map(InheritableField::Value)
583            }
584
585            fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
586            where
587                V: de::MapAccess<'de>,
588            {
589                let mvd = de::value::MapAccessDeserializer::new(map);
590                TomlInheritedField::deserialize(mvd).map(InheritableField::Inherit)
591            }
592        }
593
594        d.deserialize_any(Visitor)
595    }
596}
597
598pub type InheritableBtreeMap = InheritableField<BTreeMap<String, BTreeMap<String, String>>>;
599
600impl<'de> de::Deserialize<'de> for InheritableBtreeMap {
601    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
602    where
603        D: de::Deserializer<'de>,
604    {
605        let value = serde_value::Value::deserialize(deserializer)?;
606
607        if let Ok(w) = TomlInheritedField::deserialize(
608            serde_value::ValueDeserializer::<D::Error>::new(value.clone()),
609        ) {
610            return Ok(InheritableField::Inherit(w));
611        }
612        BTreeMap::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value))
613            .map(InheritableField::Value)
614    }
615}
616
617#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
618#[serde(rename_all = "kebab-case")]
619#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
620pub struct TomlInheritedField {
621    workspace: WorkspaceValue,
622}
623
624impl TomlInheritedField {
625    pub fn new() -> Self {
626        TomlInheritedField {
627            workspace: WorkspaceValue,
628        }
629    }
630}
631
632impl Default for TomlInheritedField {
633    fn default() -> Self {
634        Self::new()
635    }
636}
637
638#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
639#[serde(try_from = "bool")]
640#[serde(into = "bool")]
641#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
642#[cfg_attr(feature = "unstable-schema", schemars(extend("type" = "boolean", "enum" = [true])))]
643struct WorkspaceValue;
644
645impl TryFrom<bool> for WorkspaceValue {
646    type Error = String;
647    fn try_from(other: bool) -> Result<WorkspaceValue, Self::Error> {
648        if other {
649            Ok(WorkspaceValue)
650        } else {
651            Err("`workspace` cannot be false".to_owned())
652        }
653    }
654}
655
656impl From<WorkspaceValue> for bool {
657    fn from(_: WorkspaceValue) -> bool {
658        true
659    }
660}
661
662#[derive(Serialize, Clone, Debug)]
663#[serde(untagged)]
664#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
665pub enum InheritableDependency {
666    /// The type that is used when not inheriting from a workspace.
667    Value(TomlDependency),
668    /// The type when inheriting from a workspace.
669    Inherit(TomlInheritedDependency),
670}
671
672impl InheritableDependency {
673    pub fn unused_keys(&self) -> Vec<String> {
674        match self {
675            InheritableDependency::Value(d) => d.unused_keys(),
676            InheritableDependency::Inherit(w) => w._unused_keys.keys().cloned().collect(),
677        }
678    }
679
680    pub fn normalized(&self) -> Result<&TomlDependency, UnresolvedError> {
681        match self {
682            InheritableDependency::Value(d) => Ok(d),
683            InheritableDependency::Inherit(_) => Err(UnresolvedError),
684        }
685    }
686
687    pub fn is_inherited(&self) -> bool {
688        matches!(self, InheritableDependency::Inherit(_))
689    }
690}
691
692impl<'de> de::Deserialize<'de> for InheritableDependency {
693    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
694    where
695        D: de::Deserializer<'de>,
696    {
697        let value = serde_value::Value::deserialize(deserializer)?;
698
699        if let Ok(w) = TomlInheritedDependency::deserialize(serde_value::ValueDeserializer::<
700            D::Error,
701        >::new(value.clone()))
702        {
703            if w._unused_keys.get("builtin").is_some() {
704                return Err(de::Error::custom(
705                    "`builtin` cannot be combined with `workspace = true`",
706                ));
707            }
708            return if w.workspace {
709                Ok(InheritableDependency::Inherit(w))
710            } else {
711                Err(de::Error::custom("`workspace` cannot be false"))
712            };
713        }
714        TomlDependency::deserialize(serde_value::ValueDeserializer::<D::Error>::new(value))
715            .map(InheritableDependency::Value)
716    }
717}
718
719#[derive(Deserialize, Serialize, Clone, Debug)]
720#[serde(rename_all = "kebab-case")]
721#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
722pub struct TomlInheritedDependency {
723    pub workspace: bool,
724    pub features: Option<Vec<String>>,
725    pub default_features: Option<bool>,
726    #[serde(rename = "default_features")]
727    pub default_features2: Option<bool>,
728    pub optional: Option<bool>,
729    pub public: Option<bool>,
730
731    /// This is here to provide a way to see the "unused manifest keys" when deserializing
732    #[serde(skip_serializing)]
733    #[serde(flatten)]
734    #[cfg_attr(feature = "unstable-schema", schemars(skip))]
735    pub _unused_keys: BTreeMap<String, toml::Value>,
736}
737
738impl TomlInheritedDependency {
739    pub fn default_features(&self) -> Option<bool> {
740        self.default_features.or(self.default_features2)
741    }
742}
743
744#[derive(Clone, Debug, Serialize)]
745#[serde(untagged)]
746#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
747pub enum TomlDependency<P: Clone = String> {
748    /// In the simple format, only a version is specified, eg.
749    /// `package = "<version>"`
750    Simple(String),
751    /// The simple format is equivalent to a detailed dependency
752    /// specifying only a version, eg.
753    /// `package = { version = "<version>" }`
754    Detailed(TomlDetailedDependency<P>),
755}
756
757impl TomlDependency {
758    pub fn is_version_specified(&self) -> bool {
759        match self {
760            TomlDependency::Detailed(d) => d.version.is_some(),
761            TomlDependency::Simple(..) => true,
762        }
763    }
764
765    pub fn is_optional(&self) -> bool {
766        match self {
767            TomlDependency::Detailed(d) => d.optional.unwrap_or(false),
768            TomlDependency::Simple(..) => false,
769        }
770    }
771
772    pub fn is_public(&self) -> bool {
773        match self {
774            TomlDependency::Detailed(d) => d.public.unwrap_or(false),
775            TomlDependency::Simple(..) => false,
776        }
777    }
778
779    pub fn default_features(&self) -> Option<bool> {
780        match self {
781            TomlDependency::Detailed(d) => d.default_features(),
782            TomlDependency::Simple(..) => None,
783        }
784    }
785
786    pub fn unused_keys(&self) -> Vec<String> {
787        match self {
788            TomlDependency::Simple(_) => vec![],
789            TomlDependency::Detailed(detailed) => detailed._unused_keys.keys().cloned().collect(),
790        }
791    }
792}
793
794impl<'de, P: Deserialize<'de> + Clone> de::Deserialize<'de> for TomlDependency<P> {
795    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
796    where
797        D: de::Deserializer<'de>,
798    {
799        use serde::de::Error as _;
800        let expected = "a version string like \"0.9.8\" or a \
801                     detailed dependency like { version = \"0.9.8\" }";
802        UntaggedEnumVisitor::new()
803            .expecting(expected)
804            .string(|value| Ok(TomlDependency::Simple(value.to_owned())))
805            .bool(|value| {
806                let expected = format!("invalid type: boolean `{value}`, expected {expected}");
807                let err = if value {
808                    format!(
809                        "{expected}\n\
810                    note: if you meant to use a workspace member, you can write\n \
811                      dep.workspace = {value}"
812                    )
813                } else {
814                    expected
815                };
816
817                Err(serde_untagged::de::Error::custom(err))
818            })
819            .map(|value| value.deserialize().map(TomlDependency::Detailed))
820            .deserialize(deserializer)
821    }
822}
823
824#[derive(Deserialize, Serialize, Clone, Debug)]
825#[serde(rename_all = "kebab-case")]
826#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
827pub struct TomlDetailedDependency<P: Clone = String> {
828    pub version: Option<String>,
829
830    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
831    pub registry: Option<RegistryName>,
832    /// The URL of the `registry` field.
833    /// This is an internal implementation detail. When Cargo creates a
834    /// package, it replaces `registry` with `registry-index` so that the
835    /// manifest contains the correct URL. All users won't have the same
836    /// registry names configured, so Cargo can't rely on just the name for
837    /// crates published by other users.
838    pub registry_index: Option<String>,
839    // `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to
840    // that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file.
841    pub path: Option<P>,
842    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
843    pub base: Option<PathBaseName>,
844    pub git: Option<String>,
845    pub branch: Option<String>,
846    pub tag: Option<String>,
847    pub rev: Option<String>,
848    #[serde(
849        default,
850        deserialize_with = "deserialize_builtin",
851        skip_serializing_if = "std::ops::Not::not"
852    )]
853    #[cfg_attr(feature = "unstable-schema", schemars(extend("const" = true)))]
854    pub builtin: bool,
855
856    pub features: Option<Vec<String>>,
857    pub optional: Option<bool>,
858    pub default_features: Option<bool>,
859    #[serde(rename = "default_features")]
860    pub default_features2: Option<bool>,
861    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
862    pub package: Option<PackageName>,
863    pub public: Option<bool>,
864
865    /// One or more of `bin`, `cdylib`, `staticlib`, `bin:<name>`.
866    pub artifact: Option<StringOrVec>,
867    /// If set, the artifact should also be a dependency
868    pub lib: Option<bool>,
869    /// A platform name, like `x86_64-apple-darwin`
870    pub target: Option<String>,
871
872    /// This is here to provide a way to see the "unused manifest keys" when deserializing
873    #[serde(skip_serializing)]
874    #[serde(flatten)]
875    #[cfg_attr(feature = "unstable-schema", schemars(skip))]
876    pub _unused_keys: BTreeMap<String, toml::Value>,
877}
878
879fn deserialize_builtin<'de, D: de::Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
880    if bool::deserialize(deserializer)? {
881        Ok(true)
882    } else {
883        Err(de::Error::custom("`builtin` cannot be false"))
884    }
885}
886
887impl<P: Clone> TomlDetailedDependency<P> {
888    pub fn default_features(&self) -> Option<bool> {
889        self.default_features.or(self.default_features2)
890    }
891}
892
893// Explicit implementation so we avoid pulling in P: Default
894impl<P: Clone> Default for TomlDetailedDependency<P> {
895    fn default() -> Self {
896        Self {
897            version: Default::default(),
898            builtin: Default::default(),
899            registry: Default::default(),
900            registry_index: Default::default(),
901            path: Default::default(),
902            base: Default::default(),
903            git: Default::default(),
904            branch: Default::default(),
905            tag: Default::default(),
906            rev: Default::default(),
907            features: Default::default(),
908            optional: Default::default(),
909            default_features: Default::default(),
910            default_features2: Default::default(),
911            package: Default::default(),
912            public: Default::default(),
913            artifact: Default::default(),
914            lib: Default::default(),
915            target: Default::default(),
916            _unused_keys: Default::default(),
917        }
918    }
919}
920
921#[derive(Deserialize, Serialize, Clone, Debug, Default)]
922#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
923pub struct TomlProfiles(pub BTreeMap<ProfileName, TomlProfile>);
924
925impl TomlProfiles {
926    pub fn get_all(&self) -> &BTreeMap<ProfileName, TomlProfile> {
927        &self.0
928    }
929
930    pub fn get(&self, name: &str) -> Option<&TomlProfile> {
931        self.0.get(name)
932    }
933}
934
935#[derive(Deserialize, Serialize, Clone, Debug, Default, Eq, PartialEq)]
936#[serde(default, rename_all = "kebab-case")]
937#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
938pub struct TomlProfile {
939    pub opt_level: Option<TomlOptLevel>,
940    pub lto: Option<StringOrBool>,
941    pub codegen_backend: Option<String>,
942    pub codegen_units: Option<u32>,
943    pub debug: Option<TomlDebugInfo>,
944    pub split_debuginfo: Option<String>,
945    pub debug_assertions: Option<bool>,
946    pub rpath: Option<bool>,
947    pub panic: Option<String>,
948    pub overflow_checks: Option<bool>,
949    pub incremental: Option<bool>,
950    pub dir_name: Option<String>,
951    pub inherits: Option<String>,
952    pub strip: Option<StringOrBool>,
953    // Note that `rustflags` is used for the cargo-feature `profile_rustflags`
954    pub rustflags: Option<Vec<String>>,
955    // These two fields must be last because they are sub-tables, and TOML
956    // requires all non-tables to be listed first.
957    pub package: Option<BTreeMap<ProfilePackageSpec, TomlProfile>>,
958    pub build_override: Option<Box<TomlProfile>>,
959    /// Controls the scope of path sanitization in build outputs.
960    pub trim_paths: Option<TomlTrimPaths>,
961    /// Unstable feature `hint-mostly-unused`
962    pub hint_mostly_unused: Option<bool>,
963}
964
965impl TomlProfile {
966    /// Overwrite self's values with the given profile.
967    pub fn merge(&mut self, profile: &Self) {
968        if let Some(v) = &profile.opt_level {
969            self.opt_level = Some(v.clone());
970        }
971
972        if let Some(v) = &profile.lto {
973            self.lto = Some(v.clone());
974        }
975
976        if let Some(v) = &profile.codegen_backend {
977            self.codegen_backend = Some(v.clone());
978        }
979
980        if let Some(v) = profile.codegen_units {
981            self.codegen_units = Some(v);
982        }
983
984        if let Some(v) = profile.debug {
985            self.debug = Some(v);
986        }
987
988        if let Some(v) = profile.debug_assertions {
989            self.debug_assertions = Some(v);
990        }
991
992        if let Some(v) = &profile.split_debuginfo {
993            self.split_debuginfo = Some(v.clone());
994        }
995
996        if let Some(v) = profile.rpath {
997            self.rpath = Some(v);
998        }
999
1000        if let Some(v) = &profile.panic {
1001            self.panic = Some(v.clone());
1002        }
1003
1004        if let Some(v) = profile.overflow_checks {
1005            self.overflow_checks = Some(v);
1006        }
1007
1008        if let Some(v) = profile.incremental {
1009            self.incremental = Some(v);
1010        }
1011
1012        if let Some(v) = &profile.rustflags {
1013            self.rustflags = Some(v.clone());
1014        }
1015
1016        if let Some(other_package) = &profile.package {
1017            match &mut self.package {
1018                Some(self_package) => {
1019                    for (spec, other_pkg_profile) in other_package {
1020                        match self_package.get_mut(spec) {
1021                            Some(p) => p.merge(other_pkg_profile),
1022                            None => {
1023                                self_package.insert(spec.clone(), other_pkg_profile.clone());
1024                            }
1025                        }
1026                    }
1027                }
1028                None => self.package = Some(other_package.clone()),
1029            }
1030        }
1031
1032        if let Some(other_bo) = &profile.build_override {
1033            match &mut self.build_override {
1034                Some(self_bo) => self_bo.merge(other_bo),
1035                None => self.build_override = Some(other_bo.clone()),
1036            }
1037        }
1038
1039        if let Some(v) = &profile.inherits {
1040            self.inherits = Some(v.clone());
1041        }
1042
1043        if let Some(v) = &profile.dir_name {
1044            self.dir_name = Some(v.clone());
1045        }
1046
1047        if let Some(v) = &profile.strip {
1048            self.strip = Some(v.clone());
1049        }
1050
1051        if let Some(v) = &profile.trim_paths {
1052            self.trim_paths = Some(v.clone())
1053        }
1054
1055        if let Some(v) = profile.hint_mostly_unused {
1056            self.hint_mostly_unused = Some(v);
1057        }
1058    }
1059}
1060
1061#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
1062#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1063pub enum ProfilePackageSpec {
1064    Spec(PackageIdSpec),
1065    All,
1066}
1067
1068impl fmt::Display for ProfilePackageSpec {
1069    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1070        match self {
1071            ProfilePackageSpec::Spec(spec) => spec.fmt(f),
1072            ProfilePackageSpec::All => f.write_str("*"),
1073        }
1074    }
1075}
1076
1077impl ser::Serialize for ProfilePackageSpec {
1078    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
1079    where
1080        S: ser::Serializer,
1081    {
1082        self.to_string().serialize(s)
1083    }
1084}
1085
1086impl<'de> de::Deserialize<'de> for ProfilePackageSpec {
1087    fn deserialize<D>(d: D) -> Result<ProfilePackageSpec, D::Error>
1088    where
1089        D: de::Deserializer<'de>,
1090    {
1091        let string = String::deserialize(d)?;
1092        if string == "*" {
1093            Ok(ProfilePackageSpec::All)
1094        } else {
1095            PackageIdSpec::parse(&string)
1096                .map_err(de::Error::custom)
1097                .map(ProfilePackageSpec::Spec)
1098        }
1099    }
1100}
1101
1102#[derive(Clone, Debug, Eq, PartialEq)]
1103#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1104#[cfg_attr(feature = "unstable-schema", schemars(extend("type" = ["string", "integer"], "enum" = [0, 1, 2, 3, "s", "z"])))]
1105pub struct TomlOptLevel(pub String);
1106
1107impl ser::Serialize for TomlOptLevel {
1108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1109    where
1110        S: ser::Serializer,
1111    {
1112        match self.0.parse::<u32>() {
1113            Ok(n) => n.serialize(serializer),
1114            Err(_) => self.0.serialize(serializer),
1115        }
1116    }
1117}
1118
1119impl<'de> de::Deserialize<'de> for TomlOptLevel {
1120    fn deserialize<D>(d: D) -> Result<TomlOptLevel, D::Error>
1121    where
1122        D: de::Deserializer<'de>,
1123    {
1124        use serde::de::Error as _;
1125        UntaggedEnumVisitor::new()
1126            .expecting("an optimization level")
1127            .i64(|value| Ok(TomlOptLevel(value.to_string())))
1128            .string(|value| {
1129                if value == "s" || value == "z" {
1130                    Ok(TomlOptLevel(value.to_string()))
1131                } else {
1132                    Err(serde_untagged::de::Error::custom(format!(
1133                        "must be `0`, `1`, `2`, `3`, `s` or `z`, \
1134                         but found the string: \"{}\"",
1135                        value
1136                    )))
1137                }
1138            })
1139            .deserialize(d)
1140    }
1141}
1142
1143#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
1144#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1145#[cfg_attr(feature = "unstable-schema", schemars(rename_all = "kebab-case"))]
1146#[cfg_attr(feature = "unstable-schema", schemars(extend("type" = ["string", "integer", "boolean"])))]
1147#[cfg_attr(feature = "unstable-schema", schemars(transform = Self::transform))]
1148pub enum TomlDebugInfo {
1149    None,
1150    LineDirectivesOnly,
1151    LineTablesOnly,
1152    Limited,
1153    Full,
1154}
1155
1156// Add extra enum variants, preserving existing such "line-tables-only"
1157#[cfg(feature = "unstable-schema")]
1158impl TomlDebugInfo {
1159    fn transform(schema: &mut schemars::Schema) {
1160        if let Some(serde_json::Value::Array(variants)) = schema.get_mut("enum") {
1161            variants.reserve(5);
1162            variants.extend((0..=2).map(Into::into));
1163            variants.extend_from_slice(&[false.into(), true.into()]);
1164        }
1165    }
1166}
1167
1168impl Display for TomlDebugInfo {
1169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1170        match self {
1171            TomlDebugInfo::None => f.write_char('0'),
1172            TomlDebugInfo::Limited => f.write_char('1'),
1173            TomlDebugInfo::Full => f.write_char('2'),
1174            TomlDebugInfo::LineDirectivesOnly => f.write_str("line-directives-only"),
1175            TomlDebugInfo::LineTablesOnly => f.write_str("line-tables-only"),
1176        }
1177    }
1178}
1179
1180impl ser::Serialize for TomlDebugInfo {
1181    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1182    where
1183        S: ser::Serializer,
1184    {
1185        match self {
1186            Self::None => 0.serialize(serializer),
1187            Self::LineDirectivesOnly => "line-directives-only".serialize(serializer),
1188            Self::LineTablesOnly => "line-tables-only".serialize(serializer),
1189            Self::Limited => 1.serialize(serializer),
1190            Self::Full => 2.serialize(serializer),
1191        }
1192    }
1193}
1194
1195impl<'de> de::Deserialize<'de> for TomlDebugInfo {
1196    fn deserialize<D>(d: D) -> Result<TomlDebugInfo, D::Error>
1197    where
1198        D: de::Deserializer<'de>,
1199    {
1200        use serde::de::Error as _;
1201        let expecting = "a boolean, 0, 1, 2, \"none\", \"limited\", \"full\", \"line-tables-only\", or \"line-directives-only\"";
1202        UntaggedEnumVisitor::new()
1203            .expecting(expecting)
1204            .bool(|value| {
1205                Ok(if value {
1206                    TomlDebugInfo::Full
1207                } else {
1208                    TomlDebugInfo::None
1209                })
1210            })
1211            .i64(|value| {
1212                let debuginfo = match value {
1213                    0 => TomlDebugInfo::None,
1214                    1 => TomlDebugInfo::Limited,
1215                    2 => TomlDebugInfo::Full,
1216                    _ => {
1217                        return Err(serde_untagged::de::Error::invalid_value(
1218                            Unexpected::Signed(value),
1219                            &expecting,
1220                        ));
1221                    }
1222                };
1223                Ok(debuginfo)
1224            })
1225            .string(|value| {
1226                let debuginfo = match value {
1227                    "none" => TomlDebugInfo::None,
1228                    "limited" => TomlDebugInfo::Limited,
1229                    "full" => TomlDebugInfo::Full,
1230                    "line-directives-only" => TomlDebugInfo::LineDirectivesOnly,
1231                    "line-tables-only" => TomlDebugInfo::LineTablesOnly,
1232                    _ => {
1233                        return Err(serde_untagged::de::Error::invalid_value(
1234                            Unexpected::Str(value),
1235                            &expecting,
1236                        ));
1237                    }
1238                };
1239                Ok(debuginfo)
1240            })
1241            .deserialize(d)
1242    }
1243}
1244
1245#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
1246#[serde(rename_all = "kebab-case")]
1247#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1248pub enum TomlTrimPaths {
1249    None,
1250    Object,
1251    All,
1252}
1253
1254impl TomlTrimPaths {
1255    pub fn is_none(&self) -> bool {
1256        matches!(self, TomlTrimPaths::None)
1257    }
1258
1259    fn as_str(&self) -> &'static str {
1260        match self {
1261            TomlTrimPaths::None => "none",
1262            TomlTrimPaths::Object => "object",
1263            TomlTrimPaths::All => "all",
1264        }
1265    }
1266}
1267
1268impl fmt::Display for TomlTrimPaths {
1269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1270        write!(f, "{}", self.as_str())
1271    }
1272}
1273
1274pub type TomlLibTarget = TomlTarget;
1275pub type TomlBinTarget = TomlTarget;
1276pub type TomlExampleTarget = TomlTarget;
1277pub type TomlTestTarget = TomlTarget;
1278pub type TomlBenchTarget = TomlTarget;
1279
1280#[derive(Default, Serialize, Deserialize, Debug, Clone)]
1281#[serde(rename_all = "kebab-case")]
1282#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1283pub struct TomlTarget {
1284    pub name: Option<String>,
1285
1286    // The intention was to only accept `crate-type` here but historical
1287    // versions of Cargo also accepted `crate_type`, so look for both.
1288    pub crate_type: Option<Vec<String>>,
1289    #[serde(rename = "crate_type")]
1290    pub crate_type2: Option<Vec<String>>,
1291
1292    #[cfg_attr(feature = "unstable-schema", schemars(with = "Option<String>"))]
1293    pub path: Option<PathValue>,
1294    // Note that `filename` is used for the cargo-feature `different_binary_name`
1295    pub filename: Option<String>,
1296    pub test: Option<bool>,
1297    pub doctest: Option<bool>,
1298    pub bench: Option<bool>,
1299    pub doc: Option<bool>,
1300    pub doc_scrape_examples: Option<bool>,
1301    pub proc_macro: Option<bool>,
1302    #[serde(rename = "proc_macro")]
1303    pub proc_macro2: Option<bool>,
1304    pub harness: Option<bool>,
1305    pub required_features: Option<Vec<String>>,
1306    pub edition: Option<String>,
1307}
1308
1309impl TomlTarget {
1310    pub fn new() -> TomlTarget {
1311        TomlTarget::default()
1312    }
1313
1314    pub fn proc_macro(&self) -> Option<bool> {
1315        self.proc_macro.or(self.proc_macro2).or_else(|| {
1316            if let Some(types) = self.crate_types() {
1317                if types.contains(&"proc-macro".to_string()) {
1318                    return Some(true);
1319                }
1320            }
1321            None
1322        })
1323    }
1324
1325    pub fn crate_types(&self) -> Option<&Vec<String>> {
1326        self.crate_type
1327            .as_ref()
1328            .or_else(|| self.crate_type2.as_ref())
1329    }
1330}
1331
1332macro_rules! str_newtype {
1333    ($name:ident) => {
1334        /// Verified string newtype
1335        #[derive(Serialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1336        #[serde(transparent)]
1337        #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1338        pub struct $name<T: AsRef<str> = String>(T);
1339
1340        impl<T: AsRef<str>> $name<T> {
1341            pub fn into_inner(self) -> T {
1342                self.0
1343            }
1344        }
1345
1346        impl<T: AsRef<str>> AsRef<str> for $name<T> {
1347            fn as_ref(&self) -> &str {
1348                self.0.as_ref()
1349            }
1350        }
1351
1352        impl<T: AsRef<str>> std::ops::Deref for $name<T> {
1353            type Target = T;
1354
1355            fn deref(&self) -> &Self::Target {
1356                &self.0
1357            }
1358        }
1359
1360        impl<T: AsRef<str>> std::borrow::Borrow<str> for $name<T> {
1361            fn borrow(&self) -> &str {
1362                self.0.as_ref()
1363            }
1364        }
1365
1366        impl<'a> std::str::FromStr for $name<String> {
1367            type Err = restricted_names::NameValidationError;
1368
1369            fn from_str(value: &str) -> Result<Self, Self::Err> {
1370                Self::new(value.to_owned())
1371            }
1372        }
1373
1374        impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for $name<T> {
1375            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1376            where
1377                D: serde::Deserializer<'de>,
1378            {
1379                let inner = T::deserialize(deserializer)?;
1380                Self::new(inner).map_err(serde::de::Error::custom)
1381            }
1382        }
1383
1384        impl<T: AsRef<str>> Display for $name<T> {
1385            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1386                self.0.as_ref().fmt(f)
1387            }
1388        }
1389    };
1390}
1391
1392str_newtype!(PackageName);
1393
1394impl<T: AsRef<str>> PackageName<T> {
1395    /// Validated package name
1396    pub fn new(name: T) -> Result<Self, NameValidationError> {
1397        restricted_names::validate_package_name(name.as_ref())?;
1398        Ok(Self(name))
1399    }
1400}
1401
1402impl PackageName {
1403    /// Coerce a value to be a validate package name
1404    ///
1405    /// Replaces invalid values with `placeholder`
1406    pub fn sanitize(name: impl AsRef<str>, placeholder: char) -> Self {
1407        PackageName(restricted_names::sanitize_package_name(
1408            name.as_ref(),
1409            placeholder,
1410        ))
1411    }
1412}
1413
1414str_newtype!(RegistryName);
1415
1416impl<T: AsRef<str>> RegistryName<T> {
1417    /// Validated registry name
1418    pub fn new(name: T) -> Result<Self, NameValidationError> {
1419        restricted_names::validate_registry_name(name.as_ref())?;
1420        Ok(Self(name))
1421    }
1422}
1423
1424str_newtype!(ProfileName);
1425
1426impl<T: AsRef<str>> ProfileName<T> {
1427    /// Validated profile name
1428    pub fn new(name: T) -> Result<Self, NameValidationError> {
1429        restricted_names::validate_profile_name(name.as_ref())?;
1430        Ok(Self(name))
1431    }
1432}
1433
1434str_newtype!(FeatureName);
1435
1436impl<T: AsRef<str>> FeatureName<T> {
1437    /// Validated feature name
1438    pub fn new(name: T) -> Result<Self, NameValidationError> {
1439        restricted_names::validate_feature_name(name.as_ref())?;
1440        Ok(Self(name))
1441    }
1442}
1443
1444str_newtype!(PathBaseName);
1445
1446impl<T: AsRef<str>> PathBaseName<T> {
1447    /// Validated path base name
1448    pub fn new(name: T) -> Result<Self, NameValidationError> {
1449        restricted_names::validate_path_base_name(name.as_ref())?;
1450        Ok(Self(name))
1451    }
1452}
1453
1454/// Corresponds to a `target` entry, but `TomlTarget` is already used.
1455#[derive(Serialize, Deserialize, Debug, Clone)]
1456#[serde(rename_all = "kebab-case")]
1457#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1458pub struct TomlPlatform {
1459    pub dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
1460    pub build_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
1461    #[serde(rename = "build_dependencies")]
1462    pub build_dependencies2: Option<BTreeMap<PackageName, InheritableDependency>>,
1463    pub dev_dependencies: Option<BTreeMap<PackageName, InheritableDependency>>,
1464    #[serde(rename = "dev_dependencies")]
1465    pub dev_dependencies2: Option<BTreeMap<PackageName, InheritableDependency>>,
1466}
1467
1468impl TomlPlatform {
1469    pub fn dev_dependencies(&self) -> Option<&BTreeMap<PackageName, InheritableDependency>> {
1470        self.dev_dependencies
1471            .as_ref()
1472            .or(self.dev_dependencies2.as_ref())
1473    }
1474
1475    pub fn build_dependencies(&self) -> Option<&BTreeMap<PackageName, InheritableDependency>> {
1476        self.build_dependencies
1477            .as_ref()
1478            .or(self.build_dependencies2.as_ref())
1479    }
1480}
1481
1482/// Definition of a feature.
1483#[derive(Clone, Debug, Serialize)]
1484#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1485#[serde(untagged)]
1486pub enum FeatureDefinition {
1487    /// Features that this feature enables.
1488    Array(Vec<String>),
1489    /// Unstable feature `feature-metadata`. Metadata of this feature.
1490    Metadata(FeatureMetadata),
1491}
1492
1493// Implementing `Deserialize` manually allows for a better error message when the `enables` key is
1494// missing.
1495impl<'de> de::Deserialize<'de> for FeatureDefinition {
1496    fn deserialize<D>(d: D) -> Result<FeatureDefinition, D::Error>
1497    where
1498        D: de::Deserializer<'de>,
1499    {
1500        UntaggedEnumVisitor::new()
1501            .seq(|seq| {
1502                seq.deserialize::<Vec<String>>()
1503                    .map(FeatureDefinition::Array)
1504            })
1505            .map(|seq| {
1506                seq.deserialize::<FeatureMetadata>()
1507                    .map(FeatureDefinition::Metadata)
1508            })
1509            .deserialize(d)
1510    }
1511}
1512
1513impl FeatureDefinition {
1514    /// Returns the features that this feature enables.
1515    pub fn enables(&self) -> &[String] {
1516        match self {
1517            Self::Array(features) => features,
1518            Self::Metadata(FeatureMetadata {
1519                enables: features, ..
1520            }) => features,
1521        }
1522    }
1523}
1524
1525/// Unstable feature `feature-metadata`. Metadata of a feature.
1526#[derive(Clone, Debug, Deserialize, Serialize)]
1527#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1528pub struct FeatureMetadata {
1529    /// Features that this feature enables.
1530    pub enables: Vec<String>,
1531
1532    /// Documentation for the feature.
1533    pub doc: Option<String>,
1534
1535    /// This is here to provide a way to see the "unused manifest keys" when deserializing
1536    #[serde(skip_serializing)]
1537    #[serde(flatten)]
1538    #[cfg_attr(feature = "unstable-schema", schemars(skip))]
1539    pub _unused_keys: BTreeMap<String, toml::Value>,
1540}
1541
1542#[derive(Serialize, Debug, Clone)]
1543#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1544pub struct InheritableLints {
1545    #[serde(skip_serializing_if = "std::ops::Not::not")]
1546    #[cfg_attr(feature = "unstable-schema", schemars(default))]
1547    pub workspace: bool,
1548    #[serde(flatten)]
1549    pub lints: TomlLints,
1550}
1551
1552impl InheritableLints {
1553    pub fn normalized(&self) -> Result<&TomlLints, UnresolvedError> {
1554        if self.workspace {
1555            Err(UnresolvedError)
1556        } else {
1557            Ok(&self.lints)
1558        }
1559    }
1560}
1561
1562impl<'de> Deserialize<'de> for InheritableLints {
1563    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1564    where
1565        D: de::Deserializer<'de>,
1566    {
1567        struct InheritableLintsVisitor;
1568
1569        impl<'de> de::Visitor<'de> for InheritableLintsVisitor {
1570            // The type that our Visitor is going to produce.
1571            type Value = InheritableLints;
1572
1573            // Format a message stating what data this Visitor expects to receive.
1574            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1575                formatter.write_str("a lints table")
1576            }
1577
1578            // Deserialize MyMap from an abstract "map" provided by the
1579            // Deserializer. The MapAccess input is a callback provided by
1580            // the Deserializer to let us see each entry in the map.
1581            fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
1582            where
1583                M: de::MapAccess<'de>,
1584            {
1585                let mut lints = TomlLints::new();
1586                let mut workspace = false;
1587
1588                // While there are entries remaining in the input, add them
1589                // into our map.
1590                while let Some(key) = access.next_key()? {
1591                    if key == "workspace" {
1592                        workspace = match access.next_value()? {
1593                            Some(WorkspaceValue) => true,
1594                            None => false,
1595                        };
1596                    } else {
1597                        let value = access.next_value()?;
1598                        lints.insert(key, value);
1599                    }
1600                }
1601
1602                Ok(InheritableLints { workspace, lints })
1603            }
1604        }
1605
1606        deserializer.deserialize_map(InheritableLintsVisitor)
1607    }
1608}
1609
1610pub type TomlLints = BTreeMap<String, TomlToolLints>;
1611
1612pub type TomlToolLints = BTreeMap<String, TomlLint>;
1613
1614#[derive(Serialize, Debug, Clone)]
1615#[serde(untagged)]
1616#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1617pub enum TomlLint {
1618    Level(TomlLintLevel),
1619    Config(TomlLintConfig),
1620}
1621
1622impl<'de> Deserialize<'de> for TomlLint {
1623    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1624    where
1625        D: de::Deserializer<'de>,
1626    {
1627        UntaggedEnumVisitor::new()
1628            .string(|string| {
1629                TomlLintLevel::deserialize(string.into_deserializer()).map(TomlLint::Level)
1630            })
1631            .map(|map| map.deserialize().map(TomlLint::Config))
1632            .deserialize(deserializer)
1633    }
1634}
1635
1636impl TomlLint {
1637    pub fn level(&self) -> TomlLintLevel {
1638        match self {
1639            Self::Level(level) => *level,
1640            Self::Config(config) => config.level,
1641        }
1642    }
1643
1644    pub fn priority(&self) -> i8 {
1645        match self {
1646            Self::Level(_) => 0,
1647            Self::Config(config) => config.priority,
1648        }
1649    }
1650
1651    pub fn config(&self) -> Option<&toml::Table> {
1652        match self {
1653            Self::Level(_) => None,
1654            Self::Config(config) => Some(&config.config),
1655        }
1656    }
1657}
1658
1659#[derive(Serialize, Deserialize, Debug, Clone)]
1660#[serde(rename_all = "kebab-case")]
1661#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1662pub struct TomlLintConfig {
1663    pub level: TomlLintLevel,
1664    #[serde(default)]
1665    pub priority: i8,
1666    #[serde(flatten)]
1667    #[cfg_attr(
1668        feature = "unstable-schema",
1669        schemars(with = "std::collections::HashMap<String, TomlValueWrapper>")
1670    )]
1671    pub config: toml::Table,
1672}
1673
1674#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
1675#[serde(rename_all = "kebab-case")]
1676#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1677pub enum TomlLintLevel {
1678    Forbid,
1679    Deny,
1680    Warn,
1681    Allow,
1682}
1683
1684#[derive(Serialize, Deserialize, Debug, Default, Clone)]
1685#[serde(rename_all = "kebab-case")]
1686#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1687pub struct Hints {
1688    #[cfg_attr(
1689        feature = "unstable-schema",
1690        schemars(with = "Option<TomlValueWrapper>")
1691    )]
1692    pub min_opt_level: Option<toml::Value>,
1693    #[cfg_attr(
1694        feature = "unstable-schema",
1695        schemars(with = "Option<TomlValueWrapper>")
1696    )]
1697    pub mostly_unused: Option<toml::Value>,
1698}
1699
1700#[derive(Copy, Clone, Debug)]
1701pub struct InvalidCargoFeatures {}
1702
1703impl<'de> de::Deserialize<'de> for InvalidCargoFeatures {
1704    fn deserialize<D>(_d: D) -> Result<Self, D::Error>
1705    where
1706        D: de::Deserializer<'de>,
1707    {
1708        use serde::de::Error as _;
1709
1710        Err(D::Error::custom(
1711            "the field `cargo-features` should be set at the top of Cargo.toml before any tables",
1712        ))
1713    }
1714}
1715
1716/// This can be parsed from either a TOML string or array,
1717/// but is always stored as a vector.
1718#[derive(Clone, Debug, Serialize, Eq, PartialEq, PartialOrd, Ord)]
1719#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1720#[cfg_attr(feature = "unstable-schema", schemars(extend("type" = ["array", "string"])))]
1721pub struct StringOrVec(pub Vec<String>);
1722
1723impl StringOrVec {
1724    pub fn iter<'a>(&'a self) -> std::slice::Iter<'a, String> {
1725        self.0.iter()
1726    }
1727}
1728
1729impl<'de> de::Deserialize<'de> for StringOrVec {
1730    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1731    where
1732        D: de::Deserializer<'de>,
1733    {
1734        UntaggedEnumVisitor::new()
1735            .expecting("string or list of strings")
1736            .string(|value| Ok(StringOrVec(vec![value.to_owned()])))
1737            .seq(|value| value.deserialize().map(StringOrVec))
1738            .deserialize(deserializer)
1739    }
1740}
1741
1742#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
1743#[serde(untagged)]
1744#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1745pub enum StringOrBool {
1746    String(String),
1747    Bool(bool),
1748}
1749
1750impl<'de> Deserialize<'de> for StringOrBool {
1751    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1752    where
1753        D: de::Deserializer<'de>,
1754    {
1755        UntaggedEnumVisitor::new()
1756            .bool(|b| Ok(StringOrBool::Bool(b)))
1757            .string(|s| Ok(StringOrBool::String(s.to_owned())))
1758            .deserialize(deserializer)
1759    }
1760}
1761
1762#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
1763#[serde(untagged)]
1764#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1765pub enum TomlPackageBuild {
1766    /// If build scripts are disabled or enabled.
1767    /// If true, `build.rs` in the root folder will be the build script.
1768    Auto(bool),
1769
1770    /// Path of Build Script if there's just one script.
1771    SingleScript(String),
1772
1773    /// Vector of paths if multiple build script are to be used.
1774    MultipleScript(Vec<String>),
1775}
1776
1777impl<'de> Deserialize<'de> for TomlPackageBuild {
1778    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1779    where
1780        D: de::Deserializer<'de>,
1781    {
1782        UntaggedEnumVisitor::new()
1783            .bool(|b| Ok(TomlPackageBuild::Auto(b)))
1784            .string(|s| Ok(TomlPackageBuild::SingleScript(s.to_owned())))
1785            .seq(|value| value.deserialize().map(TomlPackageBuild::MultipleScript))
1786            .deserialize(deserializer)
1787    }
1788}
1789
1790#[derive(PartialEq, Clone, Debug, Serialize)]
1791#[serde(untagged)]
1792#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1793pub enum VecStringOrBool {
1794    VecString(Vec<String>),
1795    Bool(bool),
1796}
1797
1798impl<'de> de::Deserialize<'de> for VecStringOrBool {
1799    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1800    where
1801        D: de::Deserializer<'de>,
1802    {
1803        UntaggedEnumVisitor::new()
1804            .expecting("a boolean or vector of strings")
1805            .bool(|value| Ok(VecStringOrBool::Bool(value)))
1806            .seq(|value| value.deserialize().map(VecStringOrBool::VecString))
1807            .deserialize(deserializer)
1808    }
1809}
1810
1811#[derive(Clone, PartialEq, Eq)]
1812pub struct PathValue(pub PathBuf);
1813
1814impl fmt::Debug for PathValue {
1815    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1816        self.0.fmt(f)
1817    }
1818}
1819
1820impl ser::Serialize for PathValue {
1821    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1822    where
1823        S: ser::Serializer,
1824    {
1825        self.0.serialize(serializer)
1826    }
1827}
1828
1829impl<'de> de::Deserialize<'de> for PathValue {
1830    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1831    where
1832        D: de::Deserializer<'de>,
1833    {
1834        Ok(PathValue(String::deserialize(deserializer)?.into()))
1835    }
1836}
1837
1838/// Error validating names in Cargo.
1839#[derive(Debug, thiserror::Error)]
1840#[error("manifest field was not resolved")]
1841#[non_exhaustive]
1842#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
1843pub struct UnresolvedError;
1844
1845#[cfg(feature = "unstable-schema")]
1846#[test]
1847fn dump_manifest_schema() {
1848    let schema = schemars::schema_for!(crate::manifest::TomlManifest);
1849    let dump = serde_json::to_string_pretty(&schema).unwrap();
1850    snapbox::assert_data_eq!(dump, snapbox::file!("../../manifest.schema.json").raw());
1851}