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}