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}