1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Provides the various error types for this crate

#[cfg(feature = "git")]
pub use crate::index::git_remote::GitError;
#[cfg(feature = "local")]
pub use crate::index::local::LocalRegistryError;

/// The core error type for this library
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Failed to deserialize a local cache entry
    #[error(transparent)]
    Cache(#[from] CacheError),
    /// This library assumes utf-8 paths in all cases, a path was provided that
    /// was not valid utf-8
    #[error("unable to use non-utf8 path {:?}", .0)]
    NonUtf8Path(std::path::PathBuf),
    /// A user-provided string was not a valid crate name
    #[error(transparent)]
    InvalidKrateName(#[from] InvalidKrateName),
    /// An I/O error
    #[error(transparent)]
    Io(#[from] std::io::Error),
    /// An I/O error occurred trying to access a specific path
    #[error("I/O operation failed for path '{}': {}", .1, .0)]
    IoPath(#[source] std::io::Error, crate::PathBuf),
    /// A user provided URL was invalid
    #[error(transparent)]
    InvalidUrl(#[from] InvalidUrl),
    /// Failed to de/serialize JSON
    #[error(transparent)]
    Json(#[from] serde_json::Error),
    /// Failed to deserialize TOML
    #[error(transparent)]
    Toml(#[from] Box<toml_span::Error>),
    /// An index entry did not contain any versions
    #[error("index entry contained no versions for the crate")]
    NoCrateVersions,
    /// Failed to handle an HTTP response or request
    #[error(transparent)]
    Http(#[from] HttpError),
    /// An error occurred doing a git operation
    #[cfg(feature = "git")]
    #[error(transparent)]
    Git(#[from] GitError),
    /// Failed to parse a semver version or requirement
    #[error(transparent)]
    Semver(#[from] semver::Error),
    /// A local registry is invalid
    #[cfg(feature = "local")]
    #[error(transparent)]
    Local(#[from] LocalRegistryError),
    /// Failed to lock a file
    #[error(transparent)]
    Lock(#[from] crate::utils::flock::FileLockError),
}

impl From<std::path::PathBuf> for Error {
    fn from(p: std::path::PathBuf) -> Self {
        Self::NonUtf8Path(p)
    }
}

/// Various kinds of reserved names disallowed by cargo
#[derive(Debug, Copy, Clone)]
pub enum ReservedNameKind {
    /// The name is a Rust keyword
    Keyword,
    /// The name conflicts with a cargo artifact directory
    Artifact,
    /// The name has a special meaning on Windows
    Windows,
    /// The name conflicts with a Rust std library name
    Standard,
}

impl std::fmt::Display for ReservedNameKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Keyword => f.write_str("rustlang keyword"),
            Self::Artifact => f.write_str("cargo artifact"),
            Self::Windows => f.write_str("windows reserved"),
            Self::Standard => f.write_str("rustlang std library"),
        }
    }
}

/// Errors that can occur when validating a crate name
#[derive(Debug, thiserror::Error)]
pub enum InvalidKrateName {
    /// The name had an invalid length
    #[error("crate name had an invalid length of '{0}'")]
    InvalidLength(usize),
    /// The name contained an invalid character
    #[error("invalid character '{invalid}` @ {index}")]
    InvalidCharacter {
        /// The invalid character
        invalid: char,
        /// The index of the character in the provided string
        index: usize,
    },
    /// The name was one of the reserved names disallowed by cargo
    #[error("the name '{reserved}' is reserved as '{kind}`")]
    ReservedName {
        /// The name that was reserved
        reserved: &'static str,
        /// The kind of the reserved name
        kind: ReservedNameKind,
    },
}

/// An error pertaining to a bad URL provided to the API
#[derive(Debug, thiserror::Error)]
#[error("the url '{url}' is invalid")]
pub struct InvalidUrl {
    /// The invalid url
    pub url: String,
    /// The reason it is invalid
    pub source: InvalidUrlError,
}

/// The specific reason for the why the URL is invalid
#[derive(Debug, thiserror::Error)]
pub enum InvalidUrlError {
    /// Sparse HTTP registry urls must be of the form `sparse+http(s)://`
    #[error("sparse indices require the use of a url that starts with `sparse+http`")]
    MissingSparse,
    /// The `<modifier>+<scheme>://` is not supported
    #[error("the scheme modifier is unknown")]
    UnknownSchemeModifier,
    /// Unable to find the `<scheme>://`
    #[error("the scheme is missing")]
    MissingScheme,
    /// Attempted to construct a git index with a sparse URL
    #[error("attempted to create a git index for a sparse URL")]
    SparseForGit,
}

/// Errors related to a local index cache
#[derive(Debug, thiserror::Error)]
pub enum CacheError {
    /// The cache entry is malformed
    #[error("the cache entry is malformed")]
    InvalidCacheEntry,
    /// The cache version is old
    #[error("the cache entry is an old, unsupported version")]
    OutdatedCacheVersion,
    /// The cache version is newer than the version supported by this crate
    #[error("the cache entry is an unknown version, possibly written by a newer cargo version")]
    UnknownCacheVersion,
    /// The index version is newer than the version supported by this crate
    #[error(
        "the cache entry's index version is unknown, possibly written by a newer cargo version"
    )]
    UnknownIndexVersion,
    /// The revision in the cache entry did match the requested revision
    ///
    /// This can occur when a git index is fetched and a newer revision is pulled
    /// from the remote index, invalidating all local cache entries
    #[error("the cache entry's revision does not match the current revision")]
    OutdatedRevision,
    /// A crate version in the cache file was malformed
    #[error("a specific version in the cache entry is malformed")]
    InvalidCrateVersion,
}

/// Errors related to HTTP requests or responses
#[derive(Debug, thiserror::Error)]
pub enum HttpError {
    /// A [`reqwest::Error`]
    #[cfg(any(feature = "sparse", feature = "local-builder"))]
    #[error(transparent)]
    Reqwest(#[from] reqwest::Error),
    /// A status code was received that indicates user error, or possibly a
    /// remote index that does not follow the protocol supported by this crate
    #[error("status code '{code}': {msg}")]
    StatusCode {
        /// The status code
        code: http::StatusCode,
        /// The reason the status code raised an error
        msg: &'static str,
    },
    /// A [`http::Error`]
    #[error(transparent)]
    Http(#[from] http::Error),
    /// A string could not be parsed as a valid header value
    #[error(transparent)]
    InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
    /// Unable to complete an async request for an `AsyncRemoteSparseIndex` within
    /// the user allotted time
    #[error("request could not be completed in the allotted timeframe")]
    Timeout,
}