LCOV - code coverage report
Current view: top level - proxy/src/control_plane - errors.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 25.4 % 67 17
Test Date: 2025-07-16 12:29:03 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) -> 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              : }
        

Generated by: LCOV version 2.1-beta