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) -> crate::error::ErrorKind {
47 3 : match self {
48 3 : ControlPlaneError::Message(e) => match e.get_reason() {
49 0 : Reason::RoleProtected => ErrorKind::User,
50 0 : Reason::ResourceNotFound => ErrorKind::User,
51 0 : Reason::ProjectNotFound => ErrorKind::User,
52 0 : Reason::EndpointNotFound => ErrorKind::User,
53 0 : Reason::BranchNotFound => ErrorKind::User,
54 0 : Reason::RateLimitExceeded => ErrorKind::ServiceRateLimit,
55 0 : Reason::NonDefaultBranchComputeTimeExceeded => ErrorKind::Quota,
56 0 : Reason::ActiveTimeQuotaExceeded => ErrorKind::Quota,
57 0 : Reason::ComputeTimeQuotaExceeded => ErrorKind::Quota,
58 0 : Reason::WrittenDataQuotaExceeded => ErrorKind::Quota,
59 0 : Reason::DataTransferQuotaExceeded => ErrorKind::Quota,
60 0 : Reason::LogicalSizeQuotaExceeded => ErrorKind::Quota,
61 0 : Reason::ConcurrencyLimitReached => ErrorKind::ControlPlane,
62 0 : Reason::LockAlreadyTaken => ErrorKind::ControlPlane,
63 0 : Reason::RunningOperations => ErrorKind::ControlPlane,
64 0 : Reason::ActiveEndpointsLimitExceeded => ErrorKind::ControlPlane,
65 3 : Reason::Unknown => ErrorKind::ControlPlane,
66 : },
67 0 : ControlPlaneError::Transport(_) => crate::error::ErrorKind::ControlPlane,
68 : }
69 3 : }
70 : }
71 :
72 : impl CouldRetry for ControlPlaneError {
73 6 : fn could_retry(&self) -> bool {
74 6 : match self {
75 : // retry some transport errors
76 0 : Self::Transport(io) => io.could_retry(),
77 6 : Self::Message(e) => e.could_retry(),
78 : }
79 6 : }
80 : }
81 :
82 : impl From<reqwest::Error> for ControlPlaneError {
83 0 : fn from(e: reqwest::Error) -> Self {
84 0 : io::Error::other(e).into()
85 0 : }
86 : }
87 :
88 : impl From<reqwest_middleware::Error> for ControlPlaneError {
89 0 : fn from(e: reqwest_middleware::Error) -> Self {
90 0 : io::Error::other(e).into()
91 0 : }
92 : }
93 :
94 : #[derive(Debug, Error)]
95 : pub(crate) enum GetAuthInfoError {
96 : // We shouldn't include the actual secret here.
97 : #[error("Console responded with a malformed auth secret")]
98 : BadSecret,
99 :
100 : #[error(transparent)]
101 : ApiError(ControlPlaneError),
102 :
103 : /// Proxy does not know about the endpoint in advanced
104 : #[error("endpoint not found in endpoint cache")]
105 : UnknownEndpoint,
106 : }
107 :
108 : // This allows more useful interactions than `#[from]`.
109 : impl<E: Into<ControlPlaneError>> From<E> for GetAuthInfoError {
110 0 : fn from(e: E) -> Self {
111 0 : Self::ApiError(e.into())
112 0 : }
113 : }
114 :
115 : impl UserFacingError for GetAuthInfoError {
116 0 : fn to_string_client(&self) -> String {
117 0 : match self {
118 : // We absolutely should not leak any secrets!
119 0 : Self::BadSecret => REQUEST_FAILED.to_owned(),
120 : // However, API might return a meaningful error.
121 0 : Self::ApiError(e) => e.to_string_client(),
122 : // pretend like control plane returned an error.
123 0 : Self::UnknownEndpoint => REQUEST_FAILED.to_owned(),
124 : }
125 0 : }
126 : }
127 :
128 : impl ReportableError for GetAuthInfoError {
129 0 : fn get_error_kind(&self) -> crate::error::ErrorKind {
130 0 : match self {
131 0 : Self::BadSecret => crate::error::ErrorKind::ControlPlane,
132 0 : Self::ApiError(_) => crate::error::ErrorKind::ControlPlane,
133 : // we only apply endpoint filtering if control plane is under high load.
134 0 : Self::UnknownEndpoint => crate::error::ErrorKind::ServiceRateLimit,
135 : }
136 0 : }
137 : }
138 :
139 : #[derive(Debug, Error)]
140 : pub(crate) enum WakeComputeError {
141 : #[error("Console responded with a malformed compute address: {0}")]
142 : BadComputeAddress(Box<str>),
143 :
144 : #[error(transparent)]
145 : ControlPlane(ControlPlaneError),
146 :
147 : #[error("Too many connections attempts")]
148 : TooManyConnections,
149 :
150 : #[error("error acquiring resource permit: {0}")]
151 : TooManyConnectionAttempts(#[from] ApiLockError),
152 : }
153 :
154 : // This allows more useful interactions than `#[from]`.
155 : impl<E: Into<ControlPlaneError>> From<E> for WakeComputeError {
156 0 : fn from(e: E) -> Self {
157 0 : Self::ControlPlane(e.into())
158 0 : }
159 : }
160 :
161 : impl UserFacingError for WakeComputeError {
162 0 : fn to_string_client(&self) -> String {
163 0 : match self {
164 : // We shouldn't show user the address even if it's broken.
165 : // Besides, user is unlikely to care about this detail.
166 0 : Self::BadComputeAddress(_) => REQUEST_FAILED.to_owned(),
167 : // However, control plane might return a meaningful error.
168 0 : Self::ControlPlane(e) => e.to_string_client(),
169 :
170 0 : Self::TooManyConnections => self.to_string(),
171 :
172 : Self::TooManyConnectionAttempts(_) => {
173 0 : "Failed to acquire permit to connect to the database. Too many database connection attempts are currently ongoing.".to_owned()
174 : }
175 : }
176 0 : }
177 : }
178 :
179 : impl ReportableError for WakeComputeError {
180 3 : fn get_error_kind(&self) -> crate::error::ErrorKind {
181 3 : match self {
182 0 : Self::BadComputeAddress(_) => crate::error::ErrorKind::ControlPlane,
183 3 : Self::ControlPlane(e) => e.get_error_kind(),
184 0 : Self::TooManyConnections => crate::error::ErrorKind::RateLimit,
185 0 : Self::TooManyConnectionAttempts(e) => e.get_error_kind(),
186 : }
187 3 : }
188 : }
189 :
190 : impl CouldRetry for WakeComputeError {
191 3 : fn could_retry(&self) -> bool {
192 3 : match self {
193 0 : Self::BadComputeAddress(_) => false,
194 3 : Self::ControlPlane(e) => e.could_retry(),
195 0 : Self::TooManyConnections => false,
196 0 : Self::TooManyConnectionAttempts(_) => false,
197 : }
198 3 : }
199 : }
200 :
201 : #[derive(Debug, Error)]
202 : pub enum GetEndpointJwksError {
203 : #[error("endpoint not found")]
204 : EndpointNotFound,
205 :
206 : #[error("failed to build control plane request: {0}")]
207 : RequestBuild(#[source] reqwest::Error),
208 :
209 : #[error("failed to send control plane request: {0}")]
210 : RequestExecute(#[source] reqwest_middleware::Error),
211 :
212 : #[error(transparent)]
213 : ControlPlane(#[from] ControlPlaneError),
214 :
215 : #[cfg(any(test, feature = "testing"))]
216 : #[error(transparent)]
217 : TokioPostgres(#[from] tokio_postgres::Error),
218 :
219 : #[cfg(any(test, feature = "testing"))]
220 : #[error(transparent)]
221 : ParseUrl(#[from] url::ParseError),
222 :
223 : #[cfg(any(test, feature = "testing"))]
224 : #[error(transparent)]
225 : TaskJoin(#[from] tokio::task::JoinError),
226 : }
|