Skip to main content

guppy/
errors.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Contains types that describe errors and warnings that `guppy` methods can return.
5
6use crate::{PackageId, graph::feature::FeatureId};
7use Error::*;
8use camino::Utf8PathBuf;
9use std::{error, fmt};
10pub use target_spec::Error as TargetSpecError;
11
12/// Error type describing the sorts of errors `guppy` can return.
13#[derive(Debug)]
14#[non_exhaustive]
15pub enum Error {
16    /// An error occurred while executing `cargo metadata`.
17    CommandError(Box<dyn error::Error + Send + Sync>),
18    /// An error occurred while parsing `cargo metadata` JSON.
19    MetadataParseError(serde_json::Error),
20    /// An error occurred while serializing `cargo metadata` JSON.
21    MetadataSerializeError(serde_json::Error),
22    /// An error occurred while constructing a `PackageGraph` from parsed metadata.
23    PackageGraphConstructError(String),
24    /// A package ID was unknown to this `PackageGraph`.
25    UnknownPackageId(PackageId),
26    /// A feature ID was unknown to this `FeatureGraph`.
27    UnknownFeatureId(PackageId, String),
28    /// A package specified by path was unknown to this workspace.
29    UnknownWorkspacePath(Utf8PathBuf),
30    /// A package specified by name was unknown to this workspace.
31    UnknownWorkspaceName(String),
32    /// An error was returned by `target-spec`.
33    TargetSpecError(String, TargetSpecError),
34    /// An internal error occurred within this `PackageGraph`.
35    PackageGraphInternalError(String),
36    /// An internal error occurred within this `FeatureGraph`.
37    FeatureGraphInternalError(String),
38    /// A summary ID was unknown to this `PackageGraph`.
39    ///
40    /// This is present if the `summaries` feature is enabled.
41    #[cfg(feature = "summaries")]
42    UnknownSummaryId(guppy_summaries::SummaryId),
43    /// While resolving a [`PackageSetSummary`](crate::graph::summaries::PackageSetSummary),
44    /// some elements were unknown to the `PackageGraph`.
45    ///
46    /// This is present if the `summaries` feature is enabled.
47    #[cfg(feature = "summaries")]
48    UnknownPackageSetSummary {
49        /// A description attached to the error.
50        message: String,
51        /// Summary IDs that weren't known to the `PackageGraph`.
52        unknown_summary_ids: Vec<crate::graph::summaries::SummaryId>,
53        /// Workspace packages that weren't known to the `PackageGraph`.
54        unknown_workspace_members: Vec<String>,
55        /// Third-party packages that weren't known to the `PackageGraph`.
56        unknown_third_party: Vec<crate::graph::summaries::ThirdPartySummary>,
57    },
58    /// While resolving the features-only set of a
59    /// [`CargoSetInputsSummary`][summary], some entries could not be applied
60    /// to the `PackageGraph`.
61    ///
62    /// This is present if the `summaries` feature is enabled.
63    ///
64    /// [summary]: crate::graph::summaries::CargoSetInputsSummary
65    #[cfg(feature = "summaries")]
66    InvalidFeaturesOnlySummary {
67        /// Summary IDs that weren't known to the `PackageGraph`.
68        unknown_summary_ids: Vec<crate::graph::summaries::SummaryId>,
69
70        /// Features and optional dependencies that weren't known to the
71        /// `FeatureGraph`, grouped by package.
72        unknown_features: Vec<crate::graph::summaries::UnknownFeatures>,
73
74        /// Entries that do not enable anything (`base` is false and there are
75        /// no features or optional dependencies). See [`is_empty`].
76        ///
77        /// [`is_empty`]: crate::graph::summaries::FeaturesOnlySummary::is_empty
78        empty_entries: Vec<crate::graph::summaries::SummaryId>,
79    },
80    /// While rebuilding a [`CargoSetInputsSummary`][summary], the host or
81    /// target platform could not be converted to a `PlatformSpec`.
82    ///
83    /// This is present if the `summaries` feature is enabled.
84    ///
85    /// [summary]: crate::graph::summaries::CargoSetInputsSummary
86    #[cfg(feature = "summaries")]
87    InvalidPlatformSpecSummary {
88        /// Whether the host or the target platform was invalid.
89        build_platform: crate::graph::cargo::BuildPlatform,
90
91        /// The underlying error.
92        error: crate::platform::PlatformSpecSummaryError,
93    },
94    /// While resolving a [`PackageSetSummary`](crate::graph::summaries::PackageSetSummary),
95    /// an unknown external registry was encountered.
96    #[cfg(feature = "summaries")]
97    UnknownRegistryName {
98        /// A description attached to the error.
99        message: String,
100
101        /// The summary for which the name wasn't recognized.
102        summary: Box<crate::graph::summaries::ThirdPartySummary>,
103
104        /// The registry name that wasn't recognized.
105        registry_name: String,
106    },
107    /// An error occurred while serializing to TOML.
108    #[cfg(feature = "summaries")]
109    TomlSerializeError(toml::ser::Error),
110}
111
112impl Error {
113    pub(crate) fn command_error(err: cargo_metadata::Error) -> Self {
114        Error::CommandError(Box::new(err))
115    }
116
117    pub(crate) fn unknown_feature_id(feature_id: FeatureId<'_>) -> Self {
118        Error::UnknownFeatureId(
119            feature_id.package_id().clone(),
120            feature_id.label().to_string(),
121        )
122    }
123}
124
125impl fmt::Display for Error {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            CommandError(_) => write!(f, "`cargo metadata` execution failed"),
129            MetadataParseError(_) => write!(f, "`cargo metadata` returned invalid JSON output"),
130            MetadataSerializeError(_) => write!(f, "failed to serialize `cargo metadata` to JSON"),
131            PackageGraphConstructError(s) => write!(f, "failed to construct package graph: {s}"),
132            UnknownPackageId(id) => write!(f, "unknown package ID: {id}"),
133            UnknownFeatureId(package_id, feature) => {
134                write!(f, "unknown feature ID: '{package_id}/{feature}'")
135            }
136            UnknownWorkspacePath(path) => write!(f, "unknown workspace path: {path}"),
137            UnknownWorkspaceName(name) => write!(f, "unknown workspace package name: {name}"),
138            TargetSpecError(msg, _) => write!(f, "target spec error while {msg}"),
139            PackageGraphInternalError(msg) => write!(f, "internal error in package graph: {msg}"),
140            FeatureGraphInternalError(msg) => write!(f, "internal error in feature graph: {msg}"),
141            #[cfg(feature = "summaries")]
142            UnknownSummaryId(summary_id) => write!(f, "unknown summary ID: {summary_id}"),
143            #[cfg(feature = "summaries")]
144            UnknownPackageSetSummary {
145                message,
146                unknown_summary_ids,
147                unknown_workspace_members,
148                unknown_third_party,
149            } => {
150                writeln!(f, "unknown elements: {message}")?;
151                if !unknown_summary_ids.is_empty() {
152                    writeln!(f, "* unknown summary IDs:")?;
153                    for summary_id in unknown_summary_ids {
154                        writeln!(f, "  - {summary_id}")?;
155                    }
156                }
157                if !unknown_workspace_members.is_empty() {
158                    writeln!(f, "* unknown workspace names:")?;
159                    for workspace_member in unknown_workspace_members {
160                        writeln!(f, "  - {workspace_member}")?;
161                    }
162                }
163                if !unknown_third_party.is_empty() {
164                    writeln!(f, "* unknown third-party:")?;
165                    for third_party in unknown_third_party {
166                        writeln!(f, "  - {third_party}")?;
167                    }
168                }
169                Ok(())
170            }
171            #[cfg(feature = "summaries")]
172            InvalidFeaturesOnlySummary {
173                unknown_summary_ids,
174                unknown_features,
175                empty_entries,
176            } => {
177                writeln!(f, "invalid entries: resolving features-only")?;
178                if !unknown_summary_ids.is_empty() {
179                    writeln!(f, "* unknown summary IDs:")?;
180                    for summary_id in unknown_summary_ids {
181                        writeln!(f, "  - {summary_id}")?;
182                    }
183                }
184                if !unknown_features.is_empty() {
185                    writeln!(f, "* unknown features:")?;
186                    for entry in unknown_features {
187                        writeln!(f, "  - {}:", entry.summary_id)?;
188                        if !entry.features.is_empty() {
189                            writeln!(f, "    - features: {}", join_names(&entry.features))?;
190                        }
191                        if !entry.optional_deps.is_empty() {
192                            writeln!(
193                                f,
194                                "    - optional-deps: {}",
195                                join_names(&entry.optional_deps)
196                            )?;
197                        }
198                    }
199                }
200                if !empty_entries.is_empty() {
201                    writeln!(
202                        f,
203                        "* entries that enable nothing (base = false, no features):"
204                    )?;
205                    for summary_id in empty_entries {
206                        writeln!(f, "  - {summary_id}")?;
207                    }
208                }
209                Ok(())
210            }
211            #[cfg(feature = "summaries")]
212            InvalidPlatformSpecSummary {
213                build_platform,
214                error,
215            } => {
216                let which = match build_platform {
217                    crate::graph::cargo::BuildPlatform::Host => "host",
218                    crate::graph::cargo::BuildPlatform::Target => "target",
219                };
220                write!(f, "invalid {which} platform `{}`", error.triple())?;
221                error.fmt_position(f)?;
222                write!(f, " in summary")
223            }
224            #[cfg(feature = "summaries")]
225            UnknownRegistryName {
226                message,
227                summary,
228                registry_name,
229            } => {
230                writeln!(
231                    f,
232                    "unknown registry name: {message}\n* for third-party: {summary}\n* name: {registry_name}\n"
233                )
234            }
235            #[cfg(feature = "summaries")]
236            TomlSerializeError(_) => write!(f, "failed to serialize to TOML"),
237        }
238    }
239}
240
241impl error::Error for Error {
242    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
243        match self {
244            MetadataParseError(err) => Some(err),
245            MetadataSerializeError(err) => Some(err),
246            CommandError(err) => Some(err.as_ref()),
247            PackageGraphConstructError(_) => None,
248            UnknownPackageId(_) => None,
249            UnknownFeatureId(_, _) => None,
250            UnknownWorkspacePath(_) => None,
251            UnknownWorkspaceName(_) => None,
252            TargetSpecError(_, err) => Some(err),
253            PackageGraphInternalError(_) => None,
254            FeatureGraphInternalError(_) => None,
255            #[cfg(feature = "summaries")]
256            UnknownSummaryId(_) => None,
257            #[cfg(feature = "summaries")]
258            UnknownPackageSetSummary { .. } => None,
259            #[cfg(feature = "summaries")]
260            InvalidFeaturesOnlySummary { .. } => None,
261            #[cfg(feature = "summaries")]
262            InvalidPlatformSpecSummary { error, .. } => Some(error),
263            #[cfg(feature = "summaries")]
264            UnknownRegistryName { .. } => None,
265            #[cfg(feature = "summaries")]
266            TomlSerializeError(err) => Some(err),
267        }
268    }
269}
270
271#[cfg(feature = "summaries")]
272fn join_names(names: &std::collections::BTreeSet<String>) -> String {
273    names
274        .iter()
275        .map(String::as_str)
276        .collect::<Vec<_>>()
277        .join(", ")
278}
279
280/// Describes warnings emitted during feature graph construction.
281#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
282#[non_exhaustive]
283pub enum FeatureGraphWarning {
284    /// A feature that was requested is missing from a package.
285    MissingFeature {
286        /// The stage of building the feature graph where the warning occurred.
287        stage: FeatureBuildStage,
288        /// The package ID for which the feature was requested.
289        package_id: PackageId,
290        /// The name of the feature.
291        feature_name: String,
292    },
293
294    /// A self-loop on a named-feature edge was discovered (for example,
295    /// `[features] a = ["a"]`).
296    ///
297    /// Self-loops arising from a package's dependency declarations -- such
298    /// as a `path` dev-dependency on the package's own crate -- are
299    /// legitimate Cargo constructs and do not produce this warning.
300    SelfLoop {
301        /// The package ID for which the self-loop was discovered.
302        package_id: PackageId,
303        /// The name of the feature for which the self-loop was discovered.
304        feature_name: String,
305    },
306}
307
308impl fmt::Display for FeatureGraphWarning {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        use FeatureGraphWarning::*;
311        match self {
312            MissingFeature {
313                stage,
314                package_id,
315                feature_name,
316            } => write!(
317                f,
318                "{stage}: for package '{package_id}', missing feature '{feature_name}'"
319            ),
320            SelfLoop {
321                package_id,
322                feature_name,
323            } => write!(
324                f,
325                "for package '{package_id}', self-loop detected for named feature '{feature_name}'"
326            ),
327        }
328    }
329}
330
331/// Describes the stage of construction at which a feature graph warning occurred.
332#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
333#[non_exhaustive]
334pub enum FeatureBuildStage {
335    /// The warning occurred while adding edges for the `[features]` section of `Cargo.toml`.
336    AddNamedFeatureEdges {
337        /// The package ID for which edges were being added.
338        package_id: PackageId,
339        /// The feature name from which edges were being added.
340        from_feature: String,
341    },
342    /// The warning occurred while adding dependency edges.
343    AddDependencyEdges {
344        /// The package ID for which edges were being added.
345        package_id: PackageId,
346        /// The name of the dependency.
347        dep_name: String,
348    },
349}
350
351impl fmt::Display for FeatureBuildStage {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        use FeatureBuildStage::*;
354        match self {
355            AddNamedFeatureEdges {
356                package_id,
357                from_feature,
358            } => write!(
359                f,
360                "for package '{package_id}', while adding named feature edges from '{from_feature}'"
361            ),
362            AddDependencyEdges {
363                package_id,
364                dep_name,
365            } => write!(
366                f,
367                "for package '{package_id}', while adding edges for dependency '{dep_name}'",
368            ),
369        }
370    }
371}