LCOV - code coverage report
Current view: top level - proxy/src/control_plane - errors.rs (source / functions) Coverage Total Hit
Test: 20b6afc7b7f34578dcaab2b3acdaecfe91cd8bf1.info Lines: 20.2 % 84 17
Test Date: 2024-11-25 17:48:16 Functions: 11.8 % 34 4

            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              : }
        

Generated by: LCOV version 2.1-beta