Line data Source code
1 : use thiserror::Error;
2 :
3 : use crate::control_plane::client::ApiLockError;
4 : use crate::control_plane::messages::{self, ControlPlaneErrorMessage, Reason};
5 : use crate::error::{io_error, ErrorKind, ReportableError, UserFacingError};
6 : use crate::proxy::retry::CouldRetry;
7 :
8 : /// A go-to error message which doesn't leak any detail.
9 : pub(crate) const REQUEST_FAILED: &str = "Console request failed";
10 :
11 : /// Common console API error.
12 0 : #[derive(Debug, Error)]
13 : pub(crate) enum ControlPlaneError {
14 : /// Error returned by the console itself.
15 : #[error("{REQUEST_FAILED} with {0}")]
16 : Message(Box<ControlPlaneErrorMessage>),
17 :
18 : /// Various IO errors like broken pipe or malformed payload.
19 : #[error("{REQUEST_FAILED}: {0}")]
20 : Transport(#[from] std::io::Error),
21 : }
22 :
23 : impl ControlPlaneError {
24 : /// Returns HTTP status code if it's the reason for failure.
25 0 : pub(crate) fn get_reason(&self) -> messages::Reason {
26 0 : match self {
27 0 : ControlPlaneError::Message(e) => e.get_reason(),
28 0 : ControlPlaneError::Transport(_) => messages::Reason::Unknown,
29 : }
30 0 : }
31 : }
32 :
33 : impl UserFacingError for ControlPlaneError {
34 0 : fn to_string_client(&self) -> String {
35 0 : match self {
36 : // To minimize risks, only select errors are forwarded to users.
37 0 : ControlPlaneError::Message(c) => c.get_user_facing_message(),
38 0 : ControlPlaneError::Transport(_) => REQUEST_FAILED.to_owned(),
39 : }
40 0 : }
41 : }
42 :
43 : impl ReportableError for ControlPlaneError {
44 3 : fn get_error_kind(&self) -> crate::error::ErrorKind {
45 3 : match self {
46 3 : ControlPlaneError::Message(e) => match e.get_reason() {
47 0 : Reason::RoleProtected => ErrorKind::User,
48 0 : Reason::ResourceNotFound => ErrorKind::User,
49 0 : Reason::ProjectNotFound => ErrorKind::User,
50 0 : Reason::EndpointNotFound => ErrorKind::User,
51 0 : Reason::BranchNotFound => ErrorKind::User,
52 0 : Reason::RateLimitExceeded => ErrorKind::ServiceRateLimit,
53 0 : Reason::NonDefaultBranchComputeTimeExceeded => ErrorKind::Quota,
54 0 : Reason::ActiveTimeQuotaExceeded => ErrorKind::Quota,
55 0 : Reason::ComputeTimeQuotaExceeded => ErrorKind::Quota,
56 0 : Reason::WrittenDataQuotaExceeded => ErrorKind::Quota,
57 0 : Reason::DataTransferQuotaExceeded => ErrorKind::Quota,
58 0 : Reason::LogicalSizeQuotaExceeded => ErrorKind::Quota,
59 0 : Reason::ConcurrencyLimitReached => ErrorKind::ControlPlane,
60 0 : Reason::LockAlreadyTaken => ErrorKind::ControlPlane,
61 0 : Reason::RunningOperations => ErrorKind::ControlPlane,
62 0 : Reason::ActiveEndpointsLimitExceeded => ErrorKind::ControlPlane,
63 3 : Reason::Unknown => ErrorKind::ControlPlane,
64 : },
65 0 : ControlPlaneError::Transport(_) => crate::error::ErrorKind::ControlPlane,
66 : }
67 3 : }
68 : }
69 :
70 : impl CouldRetry for ControlPlaneError {
71 6 : fn could_retry(&self) -> bool {
72 6 : match self {
73 : // retry some transport errors
74 0 : Self::Transport(io) => io.could_retry(),
75 6 : Self::Message(e) => e.could_retry(),
76 : }
77 6 : }
78 : }
79 :
80 : impl From<reqwest::Error> for ControlPlaneError {
81 0 : fn from(e: reqwest::Error) -> Self {
82 0 : io_error(e).into()
83 0 : }
84 : }
85 :
86 : impl From<reqwest_middleware::Error> for ControlPlaneError {
87 0 : fn from(e: reqwest_middleware::Error) -> Self {
88 0 : io_error(e).into()
89 0 : }
90 : }
91 :
92 0 : #[derive(Debug, Error)]
93 : pub(crate) enum GetAuthInfoError {
94 : // We shouldn't include the actual secret here.
95 : #[error("Console responded with a malformed auth secret")]
96 : BadSecret,
97 :
98 : #[error(transparent)]
99 : ApiError(ControlPlaneError),
100 : }
101 :
102 : // This allows more useful interactions than `#[from]`.
103 : impl<E: Into<ControlPlaneError>> From<E> for GetAuthInfoError {
104 0 : fn from(e: E) -> Self {
105 0 : Self::ApiError(e.into())
106 0 : }
107 : }
108 :
109 : impl UserFacingError for GetAuthInfoError {
110 0 : fn to_string_client(&self) -> String {
111 0 : match self {
112 : // We absolutely should not leak any secrets!
113 0 : Self::BadSecret => REQUEST_FAILED.to_owned(),
114 : // However, API might return a meaningful error.
115 0 : Self::ApiError(e) => e.to_string_client(),
116 : }
117 0 : }
118 : }
119 :
120 : impl ReportableError for GetAuthInfoError {
121 0 : fn get_error_kind(&self) -> crate::error::ErrorKind {
122 0 : match self {
123 0 : Self::BadSecret => crate::error::ErrorKind::ControlPlane,
124 0 : Self::ApiError(_) => crate::error::ErrorKind::ControlPlane,
125 : }
126 0 : }
127 : }
128 :
129 0 : #[derive(Debug, Error)]
130 : pub(crate) enum WakeComputeError {
131 : #[error("Console responded with a malformed compute address: {0}")]
132 : BadComputeAddress(Box<str>),
133 :
134 : #[error(transparent)]
135 : ControlPlane(ControlPlaneError),
136 :
137 : #[error("Too many connections attempts")]
138 : TooManyConnections,
139 :
140 : #[error("error acquiring resource permit: {0}")]
141 : TooManyConnectionAttempts(#[from] ApiLockError),
142 : }
143 :
144 : // This allows more useful interactions than `#[from]`.
145 : impl<E: Into<ControlPlaneError>> From<E> for WakeComputeError {
146 0 : fn from(e: E) -> Self {
147 0 : Self::ControlPlane(e.into())
148 0 : }
149 : }
150 :
151 : impl UserFacingError for WakeComputeError {
152 0 : fn to_string_client(&self) -> String {
153 0 : match self {
154 : // We shouldn't show user the address even if it's broken.
155 : // Besides, user is unlikely to care about this detail.
156 0 : Self::BadComputeAddress(_) => REQUEST_FAILED.to_owned(),
157 : // However, control plane might return a meaningful error.
158 0 : Self::ControlPlane(e) => e.to_string_client(),
159 :
160 0 : Self::TooManyConnections => self.to_string(),
161 :
162 : Self::TooManyConnectionAttempts(_) => {
163 0 : "Failed to acquire permit to connect to the database. Too many database connection attempts are currently ongoing.".to_owned()
164 : }
165 : }
166 0 : }
167 : }
168 :
169 : impl ReportableError for WakeComputeError {
170 3 : fn get_error_kind(&self) -> crate::error::ErrorKind {
171 3 : match self {
172 0 : Self::BadComputeAddress(_) => crate::error::ErrorKind::ControlPlane,
173 3 : Self::ControlPlane(e) => e.get_error_kind(),
174 0 : Self::TooManyConnections => crate::error::ErrorKind::RateLimit,
175 0 : Self::TooManyConnectionAttempts(e) => e.get_error_kind(),
176 : }
177 3 : }
178 : }
179 :
180 : impl CouldRetry for WakeComputeError {
181 3 : fn could_retry(&self) -> bool {
182 3 : match self {
183 0 : Self::BadComputeAddress(_) => false,
184 3 : Self::ControlPlane(e) => e.could_retry(),
185 0 : Self::TooManyConnections => false,
186 0 : Self::TooManyConnectionAttempts(_) => false,
187 : }
188 3 : }
189 : }
190 :
191 0 : #[derive(Debug, Error)]
192 : pub enum GetEndpointJwksError {
193 : #[error("endpoint not found")]
194 : EndpointNotFound,
195 :
196 : #[error("failed to build control plane request: {0}")]
197 : RequestBuild(#[source] reqwest::Error),
198 :
199 : #[error("failed to send control plane request: {0}")]
200 : RequestExecute(#[source] reqwest_middleware::Error),
201 :
202 : #[error(transparent)]
203 : ControlPlane(#[from] ControlPlaneError),
204 :
205 : #[cfg(any(test, feature = "testing"))]
206 : #[error(transparent)]
207 : TokioPostgres(#[from] tokio_postgres::Error),
208 :
209 : #[cfg(any(test, feature = "testing"))]
210 : #[error(transparent)]
211 : ParseUrl(#[from] url::ParseError),
212 :
213 : #[cfg(any(test, feature = "testing"))]
214 : #[error(transparent)]
215 : TaskJoin(#[from] tokio::task::JoinError),
216 : }
|