1use std::{convert::From, fmt};
2
3#[derive(Debug)]
5#[non_exhaustive]
6pub enum ErrorKind {
7 Http(reqwest::Error),
9 HttpStatus(reqwest::StatusCode),
11 Auth(AuthErrorDetails),
14 Metadata(String),
16 TonicMetadata(tonic::metadata::errors::InvalidMetadataValue),
17 Jwt(jsonwebtoken::errors::Error),
19 TokenSource,
21 CredentialsJson(serde_json::Error),
23 CredentialsFile(std::io::Error),
25 TokenJson(serde_json::Error),
27 TokenData,
29 GrpcStatus(tonic::transport::Error),
30 UrlError(hyper::http::uri::InvalidUri),
31 InvalidApiUrl(String),
34 ExternalCredsSourceError(String),
35 HeaderValue(hyper::header::InvalidHeaderValue),
38 IdTokenNeedsImpersonation(IdTokenUnsupportedCredentials),
42 InvalidImpersonationUrl(String),
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum IdTokenUnsupportedCredentials {
52 AuthorizedUser,
55 ExternalAccount,
57 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#[derive(Debug)]
77pub struct AuthErrorDetails {
78 pub status: Option<reqwest::StatusCode>,
80 pub oauth_error: Option<String>,
82 pub details: Option<String>,
84}
85
86impl AuthErrorDetails {
87 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#[derive(Debug)]
127pub struct Error(Box<ErrorKind>);
128
129impl Error {
130 pub fn kind(&self) -> &ErrorKind {
132 &self.0
133 }
134
135 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
219pub type Result<T> = std::result::Result<T, Error>;