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