cargo_metadata/
lib.rs

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