error.rs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. use std::env::VarError;
  2. use std::fmt::{Display, Formatter};
  3. use std::time::Duration;
  4. #[derive(Debug)]
  5. pub enum ApiError {
  6. MissingCredentials {
  7. provider: &'static str,
  8. env_vars: &'static [&'static str],
  9. },
  10. ExpiredOAuthToken,
  11. Auth(String),
  12. InvalidApiKeyEnv(VarError),
  13. Http(reqwest::Error),
  14. Io(std::io::Error),
  15. Json(serde_json::Error),
  16. Api {
  17. status: reqwest::StatusCode,
  18. error_type: Option<String>,
  19. message: Option<String>,
  20. body: String,
  21. retryable: bool,
  22. },
  23. RetriesExhausted {
  24. attempts: u32,
  25. last_error: Box<ApiError>,
  26. },
  27. InvalidSseFrame(&'static str),
  28. BackoffOverflow {
  29. attempt: u32,
  30. base_delay: Duration,
  31. },
  32. }
  33. impl ApiError {
  34. #[must_use]
  35. pub const fn missing_credentials(
  36. provider: &'static str,
  37. env_vars: &'static [&'static str],
  38. ) -> Self {
  39. Self::MissingCredentials { provider, env_vars }
  40. }
  41. #[must_use]
  42. pub fn is_retryable(&self) -> bool {
  43. match self {
  44. Self::Http(error) => error.is_connect() || error.is_timeout() || error.is_request(),
  45. Self::Api { retryable, .. } => *retryable,
  46. Self::RetriesExhausted { last_error, .. } => last_error.is_retryable(),
  47. Self::MissingCredentials { .. }
  48. | Self::ExpiredOAuthToken
  49. | Self::Auth(_)
  50. | Self::InvalidApiKeyEnv(_)
  51. | Self::Io(_)
  52. | Self::Json(_)
  53. | Self::InvalidSseFrame(_)
  54. | Self::BackoffOverflow { .. } => false,
  55. }
  56. }
  57. }
  58. impl Display for ApiError {
  59. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  60. match self {
  61. Self::MissingCredentials { provider, env_vars } => write!(
  62. f,
  63. "missing {provider} credentials; export {} before calling the {provider} API",
  64. env_vars.join(" or ")
  65. ),
  66. Self::ExpiredOAuthToken => {
  67. write!(
  68. f,
  69. "saved OAuth token is expired and no refresh token is available"
  70. )
  71. }
  72. Self::Auth(message) => write!(f, "auth error: {message}"),
  73. Self::InvalidApiKeyEnv(error) => {
  74. write!(f, "failed to read credential environment variable: {error}")
  75. }
  76. Self::Http(error) => write!(f, "http error: {error}"),
  77. Self::Io(error) => write!(f, "io error: {error}"),
  78. Self::Json(error) => write!(f, "json error: {error}"),
  79. Self::Api {
  80. status,
  81. error_type,
  82. message,
  83. body,
  84. ..
  85. } => match (error_type, message) {
  86. (Some(error_type), Some(message)) => {
  87. write!(f, "api returned {status} ({error_type}): {message}")
  88. }
  89. _ => write!(f, "api returned {status}: {body}"),
  90. },
  91. Self::RetriesExhausted {
  92. attempts,
  93. last_error,
  94. } => write!(f, "api failed after {attempts} attempts: {last_error}"),
  95. Self::InvalidSseFrame(message) => write!(f, "invalid sse frame: {message}"),
  96. Self::BackoffOverflow {
  97. attempt,
  98. base_delay,
  99. } => write!(
  100. f,
  101. "retry backoff overflowed on attempt {attempt} with base delay {base_delay:?}"
  102. ),
  103. }
  104. }
  105. }
  106. impl std::error::Error for ApiError {}
  107. impl From<reqwest::Error> for ApiError {
  108. fn from(value: reqwest::Error) -> Self {
  109. Self::Http(value)
  110. }
  111. }
  112. impl From<std::io::Error> for ApiError {
  113. fn from(value: std::io::Error) -> Self {
  114. Self::Io(value)
  115. }
  116. }
  117. impl From<serde_json::Error> for ApiError {
  118. fn from(value: serde_json::Error) -> Self {
  119. Self::Json(value)
  120. }
  121. }
  122. impl From<VarError> for ApiError {
  123. fn from(value: VarError) -> Self {
  124. Self::InvalidApiKeyEnv(value)
  125. }
  126. }