Skip to main content

cargo_metadata/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![deny(missing_docs)]
3//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
4//! Usually used from within a `cargo-*` executable
5//!
6//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
7//! details on cargo itself.
8//!
9//! ## Examples
10//!
11//! Get the current crate's metadata without default features but with all dependency information.
12//!
13//! ```rust
14//! # use std::path::Path;
15//! # use cargo_metadata::{MetadataCommand, CargoOpt};
16//! let _metadata = MetadataCommand::new().exec().unwrap();
17//! ```
18//!
19//!
20//! If you have a program that takes `--manifest-path` as an argument, you can forward that
21//! to [MetadataCommand]:
22//!
23//! ```rust
24//! # use cargo_metadata::MetadataCommand;
25//! # use std::path::Path;
26//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
27//! let mut cmd = MetadataCommand::new();
28//! let manifest_path = match args.next() {
29//!     Some(ref p) if p == "--manifest-path" => {
30//!         cmd.manifest_path(args.next().unwrap());
31//!     }
32//!     Some(p) => {
33//!         cmd.manifest_path(p.trim_start_matches("--manifest-path="));
34//!     }
35//!     None => {}
36//! };
37//!
38//! let _metadata = cmd.exec().unwrap();
39//! ```
40//!
41//! Pass features flags, e.g. `--all-features`.
42//!
43//! ```rust
44//! # use std::path::Path;
45//! # use cargo_metadata::{MetadataCommand, CargoOpt};
46//! let _metadata = MetadataCommand::new()
47//!     .manifest_path("./Cargo.toml")
48//!     .features(CargoOpt::AllFeatures)
49//!     .exec()
50//!     .unwrap();
51//! ```
52//!
53//! Parse message-format output produced by other cargo commands.
54//! It is recommended to use crates like `escargot` to produce the [Command].
55//!
56//! ```
57//! # use std::process::{Stdio, Command};
58//! # use cargo_metadata::Message;
59//! let mut command = Command::new("cargo")
60//!     .args(&["build", "--message-format=json-render-diagnostics"])
61//!     .stdout(Stdio::piped())
62//!     .spawn()
63//!     .unwrap();
64//!
65//! let reader = std::io::BufReader::new(command.stdout.take().unwrap());
66//! for message in cargo_metadata::Message::parse_stream(reader) {
67//!     match message.unwrap() {
68//!         Message::CompilerMessage(msg) => {
69//!             println!("{:?}", msg);
70//!         },
71//!         Message::CompilerArtifact(artifact) => {
72//!             println!("{:?}", artifact);
73//!         },
74//!         Message::BuildScriptExecuted(script) => {
75//!             println!("{:?}", script);
76//!         },
77//!         Message::BuildFinished(finished) => {
78//!             println!("{:?}", finished);
79//!         },
80//!         _ => () // Unknown message
81//!     }
82//! }
83//!
84//! let output = command.wait().expect("Couldn't get cargo's exit status");
85//! ```
86
87use camino::Utf8PathBuf;
88#[cfg(feature = "builder")]
89use derive_builder::Builder;
90use std::collections::BTreeMap;
91use std::env;
92use std::ffi::OsString;
93use std::fmt;
94use std::hash::Hash;
95use std::path::PathBuf;
96use std::process::{Command, Stdio};
97use std::str::{from_utf8, FromStr};
98
99pub use camino;
100pub use cargo_platform;
101pub use semver;
102use semver::Version;
103
104#[cfg(feature = "builder")]
105pub use dependency::DependencyBuilder;
106pub use dependency::{Dependency, DependencyKind};
107use diagnostic::Diagnostic;
108pub use errors::{Error, Result};
109#[cfg(feature = "unstable")]
110pub use libtest::TestMessage;
111#[allow(deprecated)]
112pub use messages::parse_messages;
113pub use messages::{
114    Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage,
115    Message, MessageIter,
116};
117#[cfg(feature = "builder")]
118pub use messages::{
119    ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder,
120    CompilerMessageBuilder,
121};
122use serde::{Deserialize, Deserializer, Serialize};
123
124mod dependency;
125pub mod diagnostic;
126mod errors;
127#[cfg(feature = "unstable")]
128pub mod libtest;
129mod messages;
130
131macro_rules! str_newtype {
132    (
133        $(#[doc = $docs:literal])*
134        $name:ident
135    ) => {
136        $(#[doc = $docs])*
137        #[derive(Serialize, Debug, Clone, Eq, PartialOrd, Ord, Hash)]
138        #[serde(transparent)]
139        pub struct $name<T: AsRef<str> = String>(T);
140
141        impl<T: AsRef<str>> $name<T> {
142            /// Convert the wrapped string into its inner type `T`
143            pub fn into_inner(self) -> T {
144                self.0
145            }
146        }
147
148        impl<T: AsRef<str>> AsRef<str> for $name<T> {
149            fn as_ref(&self) -> &str {
150                self.0.as_ref()
151            }
152        }
153
154        impl<T: AsRef<str>> std::ops::Deref for $name<T> {
155            type Target = T;
156
157            fn deref(&self) -> &Self::Target {
158                &self.0
159            }
160        }
161
162        impl<T: AsRef<str>> std::borrow::Borrow<str> for $name<T> {
163            fn borrow(&self) -> &str {
164                self.0.as_ref()
165            }
166        }
167
168        impl<'a> std::str::FromStr for $name<String> {
169            type Err = std::convert::Infallible;
170
171            fn from_str(value: &str) -> Result<Self, Self::Err> {
172                Ok(Self::new(value.to_owned()))
173            }
174        }
175
176        impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for $name<T> {
177            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
178            where
179                D: serde::Deserializer<'de>,
180            {
181                let inner = T::deserialize(deserializer)?;
182                Ok(Self::new(inner))
183            }
184        }
185
186        impl<T: AsRef<str>> fmt::Display for $name<T> {
187            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188                self.0.as_ref().fmt(f)
189            }
190        }
191
192        // Note: The next two implementations are not based on Cargo string newtype implementations.
193
194        impl<T: AsRef<str>> $name<T> {
195            /// Create a new wrapped string
196            pub fn new(name: T) -> Self {
197                Self(name)
198            }
199        }
200
201        impl<T: AsRef<str>, Rhs: AsRef<str>> PartialEq<Rhs> for $name<T> {
202            fn eq(&self, other: &Rhs) -> bool {
203                self.as_ref() == other.as_ref()
204            }
205        }
206    };
207}
208
209str_newtype!(
210    /// Feature name newtype
211    ///
212    /// Based on [cargo-util-schema's string newtype] but with two crucial differences:
213    ///
214    /// - This newtype does not verify the wrapped string.
215    /// - This newtype allows comparison with arbitrary types that implement `AsRef<str>`.
216    ///
217    /// [cargo-util-schema's string newtype]: https://github.com/epage/cargo/blob/d8975d2901e132c02b3f6b1d107f2f50b275a058/crates/cargo-util-schemas/src/manifest/mod.rs#L1355-L1413
218    FeatureName
219);
220
221str_newtype!(
222    /// Package name newtype
223    ///
224    /// Based on [cargo-util-schema's string newtype] but with two crucial differences:
225    ///
226    /// - This newtype does not verify the wrapped string.
227    /// - This newtype allows comparison with arbitrary types that implement `AsRef<str>`.
228    ///
229    /// [cargo-util-schema's string newtype]: https://github.com/epage/cargo/blob/d8975d2901e132c02b3f6b1d107f2f50b275a058/crates/cargo-util-schemas/src/manifest/mod.rs#L1355-L1413
230    PackageName
231);
232
233/// An "opaque" identifier for a package.
234///
235/// It is possible to inspect the `repr` field, if the need arises, but its
236/// precise format is an implementation detail and is subject to change.
237///
238/// `Metadata` can be indexed by `PackageId`.
239#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
240#[serde(transparent)]
241pub struct PackageId {
242    /// The underlying string representation of id.
243    pub repr: String,
244}
245
246impl fmt::Display for PackageId {
247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
248        fmt::Display::fmt(&self.repr, f)
249    }
250}
251
252/// Helpers for default metadata fields
253fn is_null(value: &serde_json::Value) -> bool {
254    matches!(value, serde_json::Value::Null)
255}
256
257#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
258#[cfg_attr(feature = "builder", derive(Builder))]
259#[non_exhaustive]
260#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
261/// Starting point for metadata returned by `cargo metadata`
262pub struct Metadata {
263    /// A list of all crates referenced by this crate (and the crate itself)
264    pub packages: Vec<Package>,
265    /// A list of all workspace members
266    pub workspace_members: Vec<PackageId>,
267    /// The list of default workspace members
268    ///
269    /// This is not available if running with a version of Cargo older than 1.71.
270    ///
271    /// You can check whether it is available or missing using respectively
272    /// [`WorkspaceDefaultMembers::is_available`] and [`WorkspaceDefaultMembers::is_missing`].
273    #[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")]
274    pub workspace_default_members: WorkspaceDefaultMembers,
275    /// Dependencies graph
276    pub resolve: Option<Resolve>,
277    /// Workspace root
278    pub workspace_root: Utf8PathBuf,
279    /// Target directory
280    pub target_directory: Utf8PathBuf,
281    /// Build directory
282    // TODO: This should become non optional once the MSRV is at or above `1.91.0`
283    pub build_directory: Option<Utf8PathBuf>,
284    /// The workspace-level metadata object. Null if non-existent.
285    #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
286    pub workspace_metadata: serde_json::Value,
287    /// The metadata format version
288    version: usize,
289}
290
291impl Metadata {
292    /// Get the workspace's root package of this metadata instance.
293    pub fn root_package(&self) -> Option<&Package> {
294        match &self.resolve {
295            Some(resolve) => {
296                // if dependencies are resolved, use Cargo's answer
297                let root = resolve.root.as_ref()?;
298                self.packages.iter().find(|pkg| &pkg.id == root)
299            }
300            None => {
301                // if dependencies aren't resolved, check for a root package manually
302                let root_manifest_path = self.workspace_root.join("Cargo.toml");
303                self.packages
304                    .iter()
305                    .find(|pkg| pkg.manifest_path == root_manifest_path)
306            }
307        }
308    }
309
310    /// Get the workspace packages.
311    pub fn workspace_packages(&self) -> Vec<&Package> {
312        self.packages
313            .iter()
314            .filter(|&p| self.workspace_members.contains(&p.id))
315            .collect()
316    }
317
318    /// Get the workspace default packages.
319    ///
320    /// # Panics
321    ///
322    /// This will panic if running with a version of Cargo older than 1.71.
323    pub fn workspace_default_packages(&self) -> Vec<&Package> {
324        self.packages
325            .iter()
326            .filter(|&p| self.workspace_default_members.contains(&p.id))
327            .collect()
328    }
329}
330
331impl<'a> std::ops::Index<&'a PackageId> for Metadata {
332    type Output = Package;
333
334    fn index(&self, idx: &'a PackageId) -> &Self::Output {
335        self.packages
336            .iter()
337            .find(|p| p.id == *idx)
338            .unwrap_or_else(|| panic!("no package with this id: {idx:?}"))
339    }
340}
341
342#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
343#[serde(transparent)]
344/// A list of default workspace members.
345///
346/// See [`Metadata::workspace_default_members`].
347///
348/// It is only available if running a version of Cargo of 1.71 or newer.
349///
350/// # Panics
351///
352/// Dereferencing when running an older version of Cargo will panic.
353pub struct WorkspaceDefaultMembers(Option<Vec<PackageId>>);
354
355impl WorkspaceDefaultMembers {
356    /// Return `true` if the list of workspace default members is supported by
357    /// the called cargo-metadata version and `false` otherwise.
358    ///
359    /// In particular useful when parsing the output of `cargo-metadata` for
360    /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
361    /// for these versions will panic.
362    ///
363    /// Opposite of [`WorkspaceDefaultMembers::is_missing`].
364    pub fn is_available(&self) -> bool {
365        self.0.is_some()
366    }
367
368    /// Return `false` if the list of workspace default members is supported by
369    /// the called cargo-metadata version and `true` otherwise.
370    ///
371    /// In particular useful when parsing the output of `cargo-metadata` for
372    /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
373    /// for these versions will panic.
374    ///
375    /// Opposite of [`WorkspaceDefaultMembers::is_available`].
376    pub fn is_missing(&self) -> bool {
377        self.0.is_none()
378    }
379}
380
381impl core::ops::Deref for WorkspaceDefaultMembers {
382    type Target = [PackageId];
383
384    fn deref(&self) -> &Self::Target {
385        self.0
386            .as_ref()
387            .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71")
388    }
389}
390
391#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
392#[cfg_attr(feature = "builder", derive(Builder))]
393#[non_exhaustive]
394#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
395/// A dependency graph
396pub struct Resolve {
397    /// Nodes in a dependencies graph
398    pub nodes: Vec<Node>,
399
400    /// The crate for which the metadata was read.
401    pub root: Option<PackageId>,
402}
403
404impl<'a> std::ops::Index<&'a PackageId> for Resolve {
405    type Output = Node;
406
407    fn index(&self, idx: &'a PackageId) -> &Self::Output {
408        self.nodes
409            .iter()
410            .find(|p| p.id == *idx)
411            .unwrap_or_else(|| panic!("no Node with this id: {idx:?}"))
412    }
413}
414
415#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
416#[cfg_attr(feature = "builder", derive(Builder))]
417#[non_exhaustive]
418#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
419/// A node in a dependencies graph
420pub struct Node {
421    /// An opaque identifier for a package
422    pub id: PackageId,
423    /// Dependencies in a structured format.
424    ///
425    /// `deps` handles renamed dependencies whereas `dependencies` does not.
426    #[serde(default)]
427    pub deps: Vec<NodeDep>,
428
429    /// List of opaque identifiers for this node's dependencies.
430    /// It doesn't support renamed dependencies. See `deps`.
431    pub dependencies: Vec<PackageId>,
432
433    /// Features enabled on the crate
434    #[serde(default)]
435    pub features: Vec<FeatureName>,
436}
437
438#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
439#[cfg_attr(feature = "builder", derive(Builder))]
440#[non_exhaustive]
441#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
442/// A dependency in a node
443pub struct NodeDep {
444    /// The name of the dependency's library target.
445    /// If the crate was renamed, it is the new name.
446    ///
447    /// If -Zbindeps is enabled local references may result in an empty
448    /// string.
449    ///
450    /// After -Zbindeps gets stabilized, cargo has indicated this field
451    /// will become deprecated.
452    pub name: String,
453    /// Package ID (opaque unique identifier)
454    pub pkg: PackageId,
455    /// The kinds of dependencies.
456    ///
457    /// This field was added in Rust 1.41.
458    #[serde(default)]
459    pub dep_kinds: Vec<DepKindInfo>,
460}
461
462#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
463#[cfg_attr(feature = "builder", derive(Builder))]
464#[non_exhaustive]
465#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
466/// Information about a dependency kind.
467pub struct DepKindInfo {
468    /// The kind of dependency.
469    #[serde(deserialize_with = "dependency::parse_dependency_kind")]
470    pub kind: DependencyKind,
471    /// The target platform for the dependency.
472    ///
473    /// This is `None` if it is not a target dependency.
474    ///
475    /// Use the [`Display`] trait to access the contents.
476    ///
477    /// By default all platform dependencies are included in the resolve
478    /// graph. Use Cargo's `--filter-platform` flag if you only want to
479    /// include dependencies for a specific platform.
480    ///
481    /// [`Display`]: std::fmt::Display
482    pub target: Option<dependency::Platform>,
483}
484
485#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
486#[cfg_attr(feature = "builder", derive(Builder))]
487#[non_exhaustive]
488#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
489/// One or more crates described by a single `Cargo.toml`
490///
491/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
492/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
493pub struct Package {
494    /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml`
495    // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.)
496    pub name: PackageName,
497    /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml`
498    pub version: Version,
499    /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml`
500    #[serde(default)]
501    #[cfg_attr(feature = "builder", builder(default))]
502    pub authors: Vec<String>,
503    /// An opaque identifier for a package
504    pub id: PackageId,
505    /// The source of the package, e.g.
506    /// crates.io or `None` for local projects.
507    // Note that this is NOT the same as cargo_util_schemas::RegistryName
508    #[cfg_attr(feature = "builder", builder(default))]
509    pub source: Option<Source>,
510    /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml`
511    #[cfg_attr(feature = "builder", builder(default))]
512    pub description: Option<String>,
513    /// List of dependencies of this particular package
514    #[cfg_attr(feature = "builder", builder(default))]
515    pub dependencies: Vec<Dependency>,
516    /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`
517    #[cfg_attr(feature = "builder", builder(default))]
518    pub license: Option<String>,
519    /// The [`license-file` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`.
520    /// If the package is using a nonstandard license, this key may be specified instead of
521    /// `license`, and must point to a file relative to the manifest.
522    #[cfg_attr(feature = "builder", builder(default))]
523    pub license_file: Option<Utf8PathBuf>,
524    /// Targets provided by the crate (lib, bin, example, test, ...)
525    #[cfg_attr(feature = "builder", builder(default))]
526    pub targets: Vec<Target>,
527    /// Features provided by the crate, mapped to the features required by that feature.
528    #[cfg_attr(feature = "builder", builder(default))]
529    pub features: BTreeMap<String, Vec<String>>,
530    /// Path containing the `Cargo.toml`
531    pub manifest_path: Utf8PathBuf,
532    /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml`
533    #[serde(default)]
534    #[cfg_attr(feature = "builder", builder(default))]
535    pub categories: Vec<String>,
536    /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml`
537    #[serde(default)]
538    #[cfg_attr(feature = "builder", builder(default))]
539    pub keywords: Vec<String>,
540    /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml`
541    #[cfg_attr(feature = "builder", builder(default))]
542    pub readme: Option<Utf8PathBuf>,
543    /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml`
544    // can't use `url::Url` because that requires a more recent stable compiler
545    #[cfg_attr(feature = "builder", builder(default))]
546    pub repository: Option<String>,
547    /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`.
548    ///
549    /// On versions of cargo before 1.49, this will always be [`None`].
550    #[cfg_attr(feature = "builder", builder(default))]
551    pub homepage: Option<String>,
552    /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`.
553    ///
554    /// On versions of cargo before 1.49, this will always be [`None`].
555    #[cfg_attr(feature = "builder", builder(default))]
556    pub documentation: Option<String>,
557    /// The default Rust edition for the package (either what's specified in the [`edition` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-edition-field)
558    /// or defaulting to [`Edition::E2015`]).
559    ///
560    /// Beware that individual targets may specify their own edition in
561    /// [`Target::edition`].
562    #[serde(default)]
563    #[cfg_attr(feature = "builder", builder(default))]
564    pub edition: Edition,
565    /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table).
566    ///
567    /// This contents can be serialized to a struct using serde:
568    ///
569    /// ```rust
570    /// use serde::Deserialize;
571    /// use serde_json::json;
572    ///
573    /// #[derive(Debug, Deserialize)]
574    /// struct SomePackageMetadata {
575    ///     some_value: i32,
576    /// }
577    ///
578    /// let value = json!({
579    ///     "some_value": 42,
580    /// });
581    ///
582    /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
583    /// assert_eq!(package_metadata.some_value, 42);
584    ///
585    /// ```
586    #[serde(default, skip_serializing_if = "is_null")]
587    #[cfg_attr(feature = "builder", builder(default))]
588    pub metadata: serde_json::Value,
589    /// The name of a native library the package is linking to.
590    #[cfg_attr(feature = "builder", builder(default))]
591    pub links: Option<String>,
592    /// List of registries to which this package may be published (derived from the [`publish` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)).
593    ///
594    /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
595    ///
596    /// This is always `None` if running with a version of Cargo older than 1.39.
597    #[cfg_attr(feature = "builder", builder(default))]
598    pub publish: Option<Vec<String>>,
599    /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml`
600    // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.)
601    /// The default binary to run by `cargo run`.
602    ///
603    /// This is always `None` if running with a version of Cargo older than 1.55.
604    #[cfg_attr(feature = "builder", builder(default))]
605    pub default_run: Option<String>,
606    /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`.
607    /// The minimum supported Rust version of this package.
608    ///
609    /// This is always `None` if running with a version of Cargo older than 1.58.
610    #[serde(default)]
611    #[serde(deserialize_with = "deserialize_rust_version")]
612    #[cfg_attr(feature = "builder", builder(default))]
613    pub rust_version: Option<Version>,
614}
615
616#[cfg(feature = "builder")]
617impl PackageBuilder {
618    /// Construct a new `PackageBuilder` with all required fields.
619    pub fn new(
620        name: impl Into<PackageName>,
621        version: impl Into<Version>,
622        id: impl Into<PackageId>,
623        path: impl Into<Utf8PathBuf>,
624    ) -> Self {
625        Self::default()
626            .name(name)
627            .version(version)
628            .id(id)
629            .manifest_path(path)
630    }
631}
632
633impl Package {
634    /// Full path to the license file if one is present in the manifest
635    pub fn license_file(&self) -> Option<Utf8PathBuf> {
636        self.license_file.as_ref().map(|file| {
637            self.manifest_path
638                .parent()
639                .unwrap_or(&self.manifest_path)
640                .join(file)
641        })
642    }
643
644    /// Full path to the readme file if one is present in the manifest
645    pub fn readme(&self) -> Option<Utf8PathBuf> {
646        self.readme.as_ref().map(|file| {
647            self.manifest_path
648                .parent()
649                .unwrap_or(&self.manifest_path)
650                .join(file)
651        })
652    }
653}
654
655/// The source of a package such as crates.io.
656///
657/// It is possible to inspect the `repr` field, if the need arises, but its
658/// precise format is an implementation detail and is subject to change.
659#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
660#[serde(transparent)]
661pub struct Source {
662    /// The underlying string representation of a source.
663    pub repr: String,
664}
665
666impl Source {
667    /// Returns true if the source is crates.io.
668    pub fn is_crates_io(&self) -> bool {
669        self.repr == "registry+https://github.com/rust-lang/crates.io-index"
670    }
671}
672
673impl fmt::Display for Source {
674    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
675        fmt::Display::fmt(&self.repr, f)
676    }
677}
678
679#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
680#[cfg_attr(feature = "builder", derive(Builder))]
681#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
682#[non_exhaustive]
683/// A single target (lib, bin, example, ...) provided by a crate
684pub struct Target {
685    /// Name as given in the `Cargo.toml` or generated from the file name
686    pub name: String,
687    /// Kind of target.
688    ///
689    /// The possible values are `example`, `test`, `bench`, `custom-build` and
690    /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
691    /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
692    ///
693    /// Other possible values may be added in the future.
694    pub kind: Vec<TargetKind>,
695    /// Similar to `kind`, but only reports the
696    /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
697    /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
698    /// Everything that's not a proc macro or a library of some kind is reported as "bin".
699    ///
700    /// Other possible values may be added in the future.
701    #[serde(default)]
702    #[cfg_attr(feature = "builder", builder(default))]
703    pub crate_types: Vec<CrateType>,
704
705    #[serde(default)]
706    #[cfg_attr(feature = "builder", builder(default))]
707    #[serde(rename = "required-features")]
708    /// This target is built only if these features are enabled.
709    /// It doesn't apply to `lib` targets.
710    pub required_features: Vec<String>,
711    /// Path to the main source file of the target
712    pub src_path: Utf8PathBuf,
713    /// Rust edition for this target
714    #[serde(default)]
715    #[cfg_attr(feature = "builder", builder(default))]
716    pub edition: Edition,
717    /// Whether or not this target has doc tests enabled, and the target is
718    /// compatible with doc testing.
719    ///
720    /// This is always `true` if running with a version of Cargo older than 1.37.
721    #[serde(default = "default_true")]
722    #[cfg_attr(feature = "builder", builder(default = "true"))]
723    pub doctest: bool,
724    /// Whether or not this target is tested by default by `cargo test`.
725    ///
726    /// This is always `true` if running with a version of Cargo older than 1.47.
727    #[serde(default = "default_true")]
728    #[cfg_attr(feature = "builder", builder(default = "true"))]
729    pub test: bool,
730    /// Whether or not this target is documented by `cargo doc`.
731    ///
732    /// This is always `true` if running with a version of Cargo older than 1.50.
733    #[serde(default = "default_true")]
734    #[cfg_attr(feature = "builder", builder(default = "true"))]
735    pub doc: bool,
736}
737
738macro_rules! methods_target_is_kind {
739    ($($name:ident => $kind:expr),*) => {
740        $(
741            /// Return true if this target is of kind `$kind`.
742            pub fn $name(&self) -> bool {
743                self.is_kind($kind)
744            }
745        )*
746    }
747}
748
749impl Target {
750    /// Return true if this target is of the given kind.
751    pub fn is_kind(&self, name: TargetKind) -> bool {
752        self.kind.iter().any(|kind| kind == &name)
753    }
754
755    // Generate `is_*` methods for each `TargetKind`
756    methods_target_is_kind! {
757        is_lib => TargetKind::Lib,
758        is_bin => TargetKind::Bin,
759        is_example => TargetKind::Example,
760        is_test => TargetKind::Test,
761        is_bench => TargetKind::Bench,
762        is_custom_build => TargetKind::CustomBuild,
763        is_proc_macro => TargetKind::ProcMacro,
764        is_cdylib => TargetKind::CDyLib,
765        is_dylib => TargetKind::DyLib,
766        is_rlib => TargetKind::RLib,
767        is_staticlib => TargetKind::StaticLib
768    }
769}
770
771/// Kind of target.
772///
773/// The possible values are `example`, `test`, `bench`, `custom-build` and
774/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
775/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
776///
777/// Other possible values may be added in the future.
778#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
779#[non_exhaustive]
780pub enum TargetKind {
781    /// `cargo bench` target
782    #[serde(rename = "bench")]
783    Bench,
784    /// Binary executable target
785    #[serde(rename = "bin")]
786    Bin,
787    /// Custom build target
788    #[serde(rename = "custom-build")]
789    CustomBuild,
790    /// Dynamic system library target
791    #[serde(rename = "cdylib")]
792    CDyLib,
793    /// Dynamic Rust library target
794    #[serde(rename = "dylib")]
795    DyLib,
796    /// Example target
797    #[serde(rename = "example")]
798    Example,
799    /// Rust library
800    #[serde(rename = "lib")]
801    Lib,
802    /// Procedural Macro
803    #[serde(rename = "proc-macro")]
804    ProcMacro,
805    /// Rust library for use as an intermediate artifact
806    #[serde(rename = "rlib")]
807    RLib,
808    /// Static system library
809    #[serde(rename = "staticlib")]
810    StaticLib,
811    /// Test target
812    #[serde(rename = "test")]
813    Test,
814    /// Unknown type
815    #[serde(untagged)]
816    Unknown(String),
817}
818
819impl From<&str> for TargetKind {
820    fn from(value: &str) -> Self {
821        match value {
822            "example" => TargetKind::Example,
823            "test" => TargetKind::Test,
824            "bench" => TargetKind::Bench,
825            "custom-build" => TargetKind::CustomBuild,
826            "bin" => TargetKind::Bin,
827            "lib" => TargetKind::Lib,
828            "rlib" => TargetKind::RLib,
829            "dylib" => TargetKind::DyLib,
830            "cdylib" => TargetKind::CDyLib,
831            "staticlib" => TargetKind::StaticLib,
832            "proc-macro" => TargetKind::ProcMacro,
833            x => TargetKind::Unknown(x.to_string()),
834        }
835    }
836}
837
838impl FromStr for TargetKind {
839    type Err = std::convert::Infallible;
840
841    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
842        Ok(TargetKind::from(s))
843    }
844}
845
846impl fmt::Display for TargetKind {
847    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848        match self {
849            Self::Bench => "bench".fmt(f),
850            Self::Bin => "bin".fmt(f),
851            Self::CustomBuild => "custom-build".fmt(f),
852            Self::CDyLib => "cdylib".fmt(f),
853            Self::DyLib => "dylib".fmt(f),
854            Self::Example => "example".fmt(f),
855            Self::Lib => "lib".fmt(f),
856            Self::ProcMacro => "proc-macro".fmt(f),
857            Self::RLib => "rlib".fmt(f),
858            Self::StaticLib => "staticlib".fmt(f),
859            Self::Test => "test".fmt(f),
860            Self::Unknown(x) => x.fmt(f),
861        }
862    }
863}
864
865/// Similar to `kind`, but only reports the
866/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
867///
868/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
869/// Everything that's not a proc macro or a library of some kind is reported as "bin".
870///
871/// Other possible values may be added in the future.
872#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
873#[non_exhaustive]
874pub enum CrateType {
875    /// Binary executable target
876    #[serde(rename = "bin")]
877    Bin,
878    /// Dynamic system library target
879    #[serde(rename = "cdylib")]
880    CDyLib,
881    /// Dynamic Rust library target
882    #[serde(rename = "dylib")]
883    DyLib,
884    /// Rust library
885    #[serde(rename = "lib")]
886    Lib,
887    /// Procedural Macro
888    #[serde(rename = "proc-macro")]
889    ProcMacro,
890    /// Rust library for use as an intermediate artifact
891    #[serde(rename = "rlib")]
892    RLib,
893    /// Static system library
894    #[serde(rename = "staticlib")]
895    StaticLib,
896    /// Unkown type
897    #[serde(untagged)]
898    Unknown(String),
899}
900
901impl From<&str> for CrateType {
902    fn from(value: &str) -> Self {
903        match value {
904            "bin" => CrateType::Bin,
905            "lib" => CrateType::Lib,
906            "rlib" => CrateType::RLib,
907            "dylib" => CrateType::DyLib,
908            "cdylib" => CrateType::CDyLib,
909            "staticlib" => CrateType::StaticLib,
910            "proc-macro" => CrateType::ProcMacro,
911            x => CrateType::Unknown(x.to_string()),
912        }
913    }
914}
915
916impl FromStr for CrateType {
917    type Err = std::convert::Infallible;
918
919    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
920        Ok(CrateType::from(s))
921    }
922}
923
924impl fmt::Display for CrateType {
925    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
926        match self {
927            Self::Bin => "bin".fmt(f),
928            Self::CDyLib => "cdylib".fmt(f),
929            Self::DyLib => "dylib".fmt(f),
930            Self::Lib => "lib".fmt(f),
931            Self::ProcMacro => "proc-macro".fmt(f),
932            Self::RLib => "rlib".fmt(f),
933            Self::StaticLib => "staticlib".fmt(f),
934            Self::Unknown(x) => x.fmt(f),
935        }
936    }
937}
938
939/// The Rust edition
940///
941/// As of writing this comment rust editions 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
942#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
943#[non_exhaustive]
944#[derive(Default)]
945pub enum Edition {
946    /// Edition 2015
947    #[serde(rename = "2015")]
948    #[default]
949    E2015,
950    /// Edition 2018
951    #[serde(rename = "2018")]
952    E2018,
953    /// Edition 2021
954    #[serde(rename = "2021")]
955    E2021,
956    /// Edition 2024
957    #[serde(rename = "2024")]
958    E2024,
959    #[doc(hidden)]
960    #[serde(rename = "2027")]
961    _E2027,
962    #[doc(hidden)]
963    #[serde(rename = "2030")]
964    _E2030,
965}
966
967impl Edition {
968    /// Return the string representation of the edition
969    pub fn as_str(&self) -> &'static str {
970        use Edition::*;
971        match self {
972            E2015 => "2015",
973            E2018 => "2018",
974            E2021 => "2021",
975            E2024 => "2024",
976            _E2027 => "2027",
977            _E2030 => "2030",
978        }
979    }
980}
981
982impl fmt::Display for Edition {
983    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
984        f.write_str(self.as_str())
985    }
986}
987
988fn default_true() -> bool {
989    true
990}
991
992/// Cargo features flags
993#[derive(Debug, Clone)]
994pub enum CargoOpt {
995    /// Run cargo with `--features-all`
996    AllFeatures,
997    /// Run cargo with `--no-default-features`
998    NoDefaultFeatures,
999    /// Run cargo with `--features <FEATURES>`
1000    SomeFeatures(Vec<String>),
1001}
1002
1003/// A builder for configuring `cargo metadata` invocation.
1004#[derive(Debug, Clone, Default)]
1005pub struct MetadataCommand {
1006    /// Path to `cargo` executable.  If not set, this will use the
1007    /// the `$CARGO` environment variable, and if that is not set, will
1008    /// simply be `cargo`.
1009    cargo_path: Option<PathBuf>,
1010    /// Path to `Cargo.toml`
1011    manifest_path: Option<PathBuf>,
1012    /// Current directory of the `cargo metadata` process.
1013    current_dir: Option<PathBuf>,
1014    /// Output information only about workspace members and don't fetch dependencies.
1015    no_deps: bool,
1016    /// Collections of `CargoOpt::SomeFeatures(..)`
1017    features: Vec<String>,
1018    /// Latched `CargoOpt::AllFeatures`
1019    all_features: bool,
1020    /// Latched `CargoOpt::NoDefaultFeatures`
1021    no_default_features: bool,
1022    /// Arbitrary command line flags to pass to `cargo`. These will be added
1023    /// to the end of the command line invocation.
1024    other_options: Vec<String>,
1025    /// Arbitrary environment variables to set or remove (depending on
1026    /// [`Option`] value) when running `cargo`. These will be merged into the
1027    /// calling environment, overriding any which clash.
1028    env: BTreeMap<OsString, Option<OsString>>,
1029    /// Show stderr
1030    verbose: bool,
1031}
1032
1033impl MetadataCommand {
1034    /// Creates a default `cargo metadata` command, which will look for
1035    /// `Cargo.toml` in the ancestors of the current directory.
1036    pub fn new() -> MetadataCommand {
1037        MetadataCommand::default()
1038    }
1039    /// Path to `cargo` executable.  If not set, this will use the
1040    /// the `$CARGO` environment variable, and if that is not set, will
1041    /// simply be `cargo`.
1042    pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
1043        self.cargo_path = Some(path.into());
1044        self
1045    }
1046    /// Path to `Cargo.toml`
1047    pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
1048        self.manifest_path = Some(path.into());
1049        self
1050    }
1051    /// Current directory of the `cargo metadata` process.
1052    pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
1053        self.current_dir = Some(path.into());
1054        self
1055    }
1056    /// Output information only about workspace members and don't fetch dependencies.
1057    pub fn no_deps(&mut self) -> &mut MetadataCommand {
1058        self.no_deps = true;
1059        self
1060    }
1061    /// Which features to include.
1062    ///
1063    /// Call this multiple times to specify advanced feature configurations:
1064    ///
1065    /// ```no_run
1066    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1067    /// MetadataCommand::new()
1068    ///     .features(CargoOpt::NoDefaultFeatures)
1069    ///     .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
1070    ///     .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
1071    ///     // ...
1072    ///     # ;
1073    /// ```
1074    ///
1075    /// # Panics
1076    ///
1077    /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
1078    /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
1079    ///
1080    /// ```should_panic
1081    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1082    /// MetadataCommand::new()
1083    ///     .features(CargoOpt::NoDefaultFeatures)
1084    ///     .features(CargoOpt::NoDefaultFeatures) // <-- panic!
1085    ///     // ...
1086    ///     # ;
1087    /// ```
1088    ///
1089    /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
1090    ///
1091    /// ```should_panic
1092    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1093    /// MetadataCommand::new()
1094    ///     .features(CargoOpt::AllFeatures)
1095    ///     .features(CargoOpt::AllFeatures) // <-- panic!
1096    ///     // ...
1097    ///     # ;
1098    /// ```
1099    pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
1100        match features {
1101            CargoOpt::SomeFeatures(features) => self.features.extend(features),
1102            CargoOpt::NoDefaultFeatures => {
1103                assert!(
1104                    !self.no_default_features,
1105                    "Do not supply CargoOpt::NoDefaultFeatures more than once!"
1106                );
1107                self.no_default_features = true;
1108            }
1109            CargoOpt::AllFeatures => {
1110                assert!(
1111                    !self.all_features,
1112                    "Do not supply CargoOpt::AllFeatures more than once!"
1113                );
1114                self.all_features = true;
1115            }
1116        }
1117        self
1118    }
1119    /// Arbitrary command line flags to pass to `cargo`.  These will be added
1120    /// to the end of the command line invocation.
1121    pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
1122        self.other_options = options.into();
1123        self
1124    }
1125
1126    /// Arbitrary environment variables to set when running `cargo`.  These will be merged into
1127    /// the calling environment, overriding any which clash.
1128    ///
1129    /// Some examples of when you may want to use this:
1130    /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
1131    ///    `CARGO_NET_GIT_FETCH_WITH_CLI=true`
1132    /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
1133    ///    the way cargo expects by default.
1134    ///
1135    /// ```no_run
1136    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1137    /// MetadataCommand::new()
1138    ///     .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
1139    ///     .env("RUSTC", "/path/to/rustc")
1140    ///     // ...
1141    ///     # ;
1142    /// ```
1143    pub fn env<K: Into<OsString>, V: Into<OsString>>(
1144        &mut self,
1145        key: K,
1146        val: V,
1147    ) -> &mut MetadataCommand {
1148        self.env.insert(key.into(), Some(val.into()));
1149        self
1150    }
1151
1152    /// Arbitrary environment variables to remove when running `cargo`.  These will be merged into
1153    /// the calling environment, overriding any which clash.
1154    ///
1155    /// Some examples of when you may want to use this:
1156    /// - Removing inherited environment variables in build scripts that can cause an error
1157    ///   when calling `cargo metadata` (for example, when cross-compiling).
1158    ///
1159    /// ```no_run
1160    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1161    /// MetadataCommand::new()
1162    ///     .env_remove("CARGO_ENCODED_RUSTFLAGS")
1163    ///     // ...
1164    ///     # ;
1165    /// ```
1166    pub fn env_remove<K: Into<OsString>>(&mut self, key: K) -> &mut MetadataCommand {
1167        self.env.insert(key.into(), None);
1168        self
1169    }
1170
1171    /// Set whether to show stderr
1172    pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
1173        self.verbose = verbose;
1174        self
1175    }
1176
1177    /// Builds a command for `cargo metadata`.  This is the first
1178    /// part of the work of `exec`.
1179    pub fn cargo_command(&self) -> Command {
1180        let cargo = self
1181            .cargo_path
1182            .clone()
1183            .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
1184            .unwrap_or_else(|| PathBuf::from("cargo"));
1185        let mut cmd = Command::new(cargo);
1186        cmd.args(["metadata", "--format-version", "1"]);
1187
1188        if self.no_deps {
1189            cmd.arg("--no-deps");
1190        }
1191
1192        if let Some(path) = self.current_dir.as_ref() {
1193            cmd.current_dir(path);
1194        }
1195
1196        if !self.features.is_empty() {
1197            cmd.arg("--features").arg(self.features.join(","));
1198        }
1199        if self.all_features {
1200            cmd.arg("--all-features");
1201        }
1202        if self.no_default_features {
1203            cmd.arg("--no-default-features");
1204        }
1205
1206        if let Some(manifest_path) = &self.manifest_path {
1207            cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
1208        }
1209        cmd.args(&self.other_options);
1210
1211        for (key, val) in &self.env {
1212            match val {
1213                Some(val) => cmd.env(key, val),
1214                None => cmd.env_remove(key),
1215            };
1216        }
1217
1218        cmd
1219    }
1220
1221    /// Parses `cargo metadata` output.  `data` must have been
1222    /// produced by a command built with `cargo_command`.
1223    pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
1224        let meta = serde_json::from_str(data.as_ref())?;
1225        Ok(meta)
1226    }
1227
1228    /// Runs configured `cargo metadata` and returns parsed `Metadata`.
1229    pub fn exec(&self) -> Result<Metadata> {
1230        let mut command = self.cargo_command();
1231        if self.verbose {
1232            command.stderr(Stdio::inherit());
1233        }
1234        let output = command.output()?;
1235        if !output.status.success() {
1236            return Err(Error::CargoMetadata {
1237                stderr: String::from_utf8(output.stderr)?,
1238            });
1239        }
1240        let stdout = from_utf8(&output.stdout)?
1241            .lines()
1242            .find(|line| line.starts_with('{'))
1243            .ok_or(Error::NoJson)?;
1244        Self::parse(stdout)
1245    }
1246}
1247
1248/// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must:
1249///
1250/// > be a bare version number with two or three components;
1251/// > it cannot include semver operators or pre-release identifiers.
1252///
1253/// [`semver::Version`] however requires three components. This function takes
1254/// care of appending `.0` if the provided version number only has two components
1255/// and ensuring that it does not contain a pre-release version or build metadata.
1256fn deserialize_rust_version<'de, D>(
1257    deserializer: D,
1258) -> std::result::Result<Option<Version>, D::Error>
1259where
1260    D: Deserializer<'de>,
1261{
1262    let mut buf = match Option::<String>::deserialize(deserializer)? {
1263        None => return Ok(None),
1264        Some(buf) => buf,
1265    };
1266
1267    for char in buf.chars() {
1268        if char == '-' {
1269            return Err(serde::de::Error::custom(
1270                "pre-release identifiers are not supported in rust-version",
1271            ));
1272        } else if char == '+' {
1273            return Err(serde::de::Error::custom(
1274                "build metadata is not supported in rust-version",
1275            ));
1276        }
1277    }
1278
1279    if buf.matches('.').count() == 1 {
1280        // e.g. 1.0 -> 1.0.0
1281        buf.push_str(".0");
1282    }
1283
1284    Ok(Some(
1285        Version::parse(&buf).map_err(serde::de::Error::custom)?,
1286    ))
1287}
1288
1289#[cfg(test)]
1290mod test {
1291    use semver::Version;
1292
1293    #[derive(Debug, serde::Deserialize)]
1294    struct BareVersion(
1295        #[serde(deserialize_with = "super::deserialize_rust_version")] Option<semver::Version>,
1296    );
1297
1298    fn bare_version(str: &str) -> Version {
1299        serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1300            .unwrap()
1301            .0
1302            .unwrap()
1303    }
1304
1305    fn bare_version_err(str: &str) -> String {
1306        serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1307            .unwrap_err()
1308            .to_string()
1309    }
1310
1311    #[test]
1312    fn test_deserialize_rust_version() {
1313        assert_eq!(bare_version("1.2"), Version::new(1, 2, 0));
1314        assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0));
1315        assert_eq!(
1316            bare_version_err("1.2.0-alpha"),
1317            "pre-release identifiers are not supported in rust-version"
1318        );
1319        assert_eq!(
1320            bare_version_err("1.2.0+123"),
1321            "build metadata is not supported in rust-version"
1322        );
1323    }
1324
1325    #[test]
1326    fn package_name_eq() {
1327        let my_package_name = super::PackageName::new("my_package");
1328        assert_eq!(my_package_name, "my_package");
1329    }
1330}