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