LCOV - code coverage report
Current view: top level - proxy/src/control_plane - errors.rs (source / functions) Coverage Total Hit
Test: 553e39c2773e5840c720c90d86e56f89a4330d43.info Lines: 20.7 % 82 17
Test Date: 2025-06-13 20:01:21 Functions: 21.1 % 19 4

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

Generated by: LCOV version 2.1-beta