Skip to main content

gcloud_sdk/
error.rs

1use std::{convert::From, fmt};
2
3/// Represents the details of the [`Error`](struct.Error.html)
4#[derive(Debug)]
5#[non_exhaustive]
6pub enum ErrorKind {
7    /// Errors that can possibly occur while accessing an HTTP server.
8    Http(reqwest::Error),
9    /// Http status code that is not 2xx when getting token.
10    HttpStatus(reqwest::StatusCode),
11    /// Authentication failed while obtaining or refreshing a token.
12    /// Distinguishes auth failures from errors of the API call itself.
13    Auth(AuthErrorDetails),
14    /// GCE metadata service error.
15    Metadata(String),
16    TonicMetadata(tonic::metadata::errors::InvalidMetadataValue),
17    /// JWT encode/decode error.
18    Jwt(jsonwebtoken::errors::Error),
19    /// Token source error.
20    TokenSource,
21    /// An error parsing credentials file.
22    CredentialsJson(serde_json::Error),
23    /// An error reading credentials file.
24    CredentialsFile(std::io::Error),
25    /// An error from json serialization and deserialization.
26    TokenJson(serde_json::Error),
27    /// Invalid token error.
28    TokenData,
29    GrpcStatus(tonic::transport::Error),
30    UrlError(hyper::http::uri::InvalidUri),
31    /// An API URL that parses but cannot be connected to: no scheme, a scheme
32    /// other than `http` or `https`, or no host.
33    InvalidApiUrl(String),
34    ExternalCredsSourceError(String),
35    /// A header value built from user input (user agent, headers, the token itself)
36    /// failed HTTP header validation (e.g. contained a control character).
37    HeaderValue(hyper::header::InvalidHeaderValue),
38    /// The credentials hold no service account to mint an ID token as. An ID token
39    /// needs a service account to impersonate, given to
40    /// [`IdTokenSource::impersonating`](crate::IdTokenSource::impersonating).
41    IdTokenNeedsImpersonation(IdTokenUnsupportedCredentials),
42    /// A `service_account_impersonation_url` in a credentials file that names no
43    /// service account.
44    InvalidImpersonationUrl(String),
45}
46
47/// Credentials that cannot mint an ID token without impersonating a service account
48/// (see [`ErrorKind::IdTokenNeedsImpersonation`]).
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum IdTokenUnsupportedCredentials {
52    /// `authorized_user` credentials, such as those `gcloud auth application-default login`
53    /// writes.
54    AuthorizedUser,
55    /// `external_account` credentials without a `service_account_impersonation_url`.
56    ExternalAccount,
57    /// An access token source given as
58    /// [`TokenSourceType::ExternalSource`](crate::TokenSourceType::ExternalSource).
59    ExternalSource,
60}
61
62impl fmt::Display for IdTokenUnsupportedCredentials {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::AuthorizedUser => write!(f, "authorized_user credentials"),
66            Self::ExternalAccount => write!(
67                f,
68                "external_account credentials without service_account_impersonation_url"
69            ),
70            Self::ExternalSource => write!(f, "an external token source"),
71        }
72    }
73}
74
75/// Details of an authentication failure (see [`ErrorKind::Auth`]).
76#[derive(Debug)]
77pub struct AuthErrorDetails {
78    /// HTTP status returned by the authentication endpoint, if any.
79    pub status: Option<reqwest::StatusCode>,
80    /// OAuth error code from the response body (e.g. `invalid_grant`), if present.
81    pub oauth_error: Option<String>,
82    /// Human readable details: the OAuth `error_description` or the raw response body.
83    pub details: Option<String>,
84}
85
86impl AuthErrorDetails {
87    /// A remediation hint for well-known OAuth error codes, if one is available.
88    pub fn hint(&self) -> Option<&'static str> {
89        match self.oauth_error.as_deref() {
90            Some("invalid_grant") => Some(
91                "the credentials are likely expired or revoked; re-authenticate (e.g. `gcloud auth application-default login`) or provide a new service account key",
92            ),
93            _ => None,
94        }
95    }
96}
97
98impl fmt::Display for AuthErrorDetails {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        let mut separate = false;
101        if let Some(ref status) = self.status {
102            write!(f, "HTTP {}", status)?;
103            separate = true;
104        }
105        if let Some(ref e) = self.oauth_error {
106            if separate {
107                write!(f, ", ")?;
108            }
109            write!(f, "oauth error: {}", e)?;
110            separate = true;
111        }
112        if let Some(ref d) = self.details {
113            if separate {
114                write!(f, " ")?;
115            }
116            write!(f, "({})", d)?;
117        }
118        if let Some(hint) = self.hint() {
119            write!(f, ". Hint: {}", hint)?;
120        }
121        Ok(())
122    }
123}
124
125/// Represents errors that can occur during getting token.
126#[derive(Debug)]
127pub struct Error(Box<ErrorKind>);
128
129impl Error {
130    /// Borrow [`ErrorKind`](enum.ErrorKind.html).
131    pub fn kind(&self) -> &ErrorKind {
132        &self.0
133    }
134
135    /// To own [`ErrorKind`](enum.ErrorKind.html).
136    pub fn into_kind(self) -> ErrorKind {
137        *self.0
138    }
139}
140
141impl fmt::Display for Error {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        use ErrorKind::*;
144        match *self.0 {
145            Http(ref e) => write!(f, "http error: {}", e),
146            HttpStatus(ref s) => write!(f, "http status error: {}", s),
147            Auth(ref details) => write!(f, "authentication error: {}", details),
148            Metadata(ref e) => write!(f, "gce metadata service error: {}", e),
149            Jwt(ref e) => write!(f, "jwt error: {}", e),
150            TokenSource => write!(f, "token source error: not found token source"),
151            CredentialsJson(ref e) => write!(f, "credentials json error: {}", e),
152            CredentialsFile(ref e) => write!(f, "credentials file error: {}", e),
153            TokenJson(ref e) => write!(f, "token json error: {}", e),
154            TokenData => write!(f, "token data error: invalid token response data"),
155            GrpcStatus(ref e) => write!(f, "Tonic/gRPC error: {}", e),
156            TonicMetadata(ref e) => write!(f, "Tonic metadata error: {}", e),
157            UrlError(ref e) => write!(f, "Url error: {}", e),
158            InvalidApiUrl(ref e) => write!(f, "Invalid API URL: {}", e),
159            ExternalCredsSourceError(ref e) => write!(f, "External creds source error: {}", e),
160            HeaderValue(ref e) => write!(f, "invalid header value: {}", e),
161            IdTokenNeedsImpersonation(ref credentials) => write!(
162                f,
163                "{} cannot mint an ID token; give a service account to impersonate",
164                credentials
165            ),
166            InvalidImpersonationUrl(ref url) => write!(
167                f,
168                "service_account_impersonation_url names no service account: {}",
169                url
170            ),
171        }
172    }
173}
174
175impl std::error::Error for Error {}
176
177impl From<reqwest::Error> for Error {
178    fn from(e: reqwest::Error) -> Self {
179        ErrorKind::Http(e).into()
180    }
181}
182
183impl From<jsonwebtoken::errors::Error> for Error {
184    fn from(e: jsonwebtoken::errors::Error) -> Self {
185        ErrorKind::Jwt(e).into()
186    }
187}
188
189impl From<ErrorKind> for Error {
190    fn from(k: ErrorKind) -> Self {
191        Error(Box::new(k))
192    }
193}
194
195impl From<tonic::transport::Error> for Error {
196    fn from(e: tonic::transport::Error) -> Self {
197        ErrorKind::GrpcStatus(e).into()
198    }
199}
200
201impl From<tonic::metadata::errors::InvalidMetadataValue> for Error {
202    fn from(e: tonic::metadata::errors::InvalidMetadataValue) -> Self {
203        ErrorKind::TonicMetadata(e).into()
204    }
205}
206
207impl From<hyper::http::uri::InvalidUri> for Error {
208    fn from(e: hyper::http::uri::InvalidUri) -> Self {
209        ErrorKind::UrlError(e).into()
210    }
211}
212
213impl From<hyper::header::InvalidHeaderValue> for Error {
214    fn from(e: hyper::header::InvalidHeaderValue) -> Self {
215        ErrorKind::HeaderValue(e).into()
216    }
217}
218
219/// Wrapper for the `Result` type with an [`Error`](struct.Error.html).
220pub type Result<T> = std::result::Result<T, Error>;