mattrguerra/cortexmainPrivate on GitHub, published here in full

error.rs

Language Rust3.7 KB94 lines

crates/cortex-core/src/provider/error.rs

  1. //! Provider failures, mapped to what a person can act on.
  2. //!
  3. //! There is no automatic retry anywhere: a retry is another billed request,
  4. //! so the UI offers one and the person decides.
  5. use serde::Serialize;
  6. #[derive(Debug, Clone, PartialEq, thiserror::Error)]
  7. pub enum ProviderError {
  8. #[error("The API key was rejected. Check it in Settings.")]
  9. InvalidKey,
  10. #[error("The account is out of credit.")]
  11. InsufficientBalance,
  12. #[error("Rate limited by the provider{}.", retry_after.map(|s| format!("; try again in {s} s")).unwrap_or_default())]
  13. RateLimited { retry_after: Option<u64> },
  14. #[error("The provider had an error ({status}): {message}")]
  15. Upstream { status: u16, message: String },
  16. #[error("The request was refused ({status}): {message}")]
  17. BadRequest { status: u16, message: String },
  18. #[error("Could not reach {url}. Is the server running?")]
  19. Unreachable { url: String },
  20. #[error("The stream stopped with an error: {0}")]
  21. Stream(String),
  22. #[error("{0}")]
  23. Other(String),
  24. }
  25. impl ProviderError {
  26. /// A stable machine name, stored with a failed message and used by the UI.
  27. pub fn kind(&self) -> &'static str {
  28. match self {
  29. Self::InvalidKey => "invalid_key",
  30. Self::InsufficientBalance => "insufficient_balance",
  31. Self::RateLimited { .. } => "rate_limited",
  32. Self::Upstream { .. } => "upstream",
  33. Self::BadRequest { .. } => "bad_request",
  34. Self::Unreachable { .. } => "unreachable",
  35. Self::Stream(_) => "stream",
  36. Self::Other(_) => "other",
  37. }
  38. }
  39. /// Map an HTTP error response. `body` is the response text, if any.
  40. pub fn from_status(status: u16, body: &str, retry_after: Option<u64>) -> Self {
  41. let message = error_message(body).unwrap_or_else(|| body.chars().take(300).collect());
  42. match status {
  43. 401 | 403 => Self::InvalidKey,
  44. 402 => Self::InsufficientBalance,
  45. 429 => Self::RateLimited { retry_after },
  46. 500..=599 => Self::Upstream { status, message },
  47. _ => Self::BadRequest { status, message },
  48. }
  49. }
  50. }
  51. /// `{"error": {"message": ...}}`, `{"error": "..."}` or `{"detail": ...}`.
  52. pub fn error_message(body: &str) -> Option<String> {
  53. let v: serde_json::Value = serde_json::from_str(body).ok()?;
  54. let e = v.get("error").or_else(|| v.get("detail"))?;
  55. match e {
  56. serde_json::Value::String(s) => Some(s.clone()),
  57. serde_json::Value::Object(o) => o.get("message").and_then(|m| m.as_str()).map(str::to_string),
  58. _ => None,
  59. }
  60. }
  61. impl Serialize for ProviderError {
  62. fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
  63. use serde::ser::SerializeStruct;
  64. let mut st = s.serialize_struct("ProviderError", 2)?;
  65. st.serialize_field("kind", self.kind())?;
  66. st.serialize_field("message", &self.to_string())?;
  67. st.end()
  68. }
  69. }
  70. #[cfg(test)]
  71. mod tests {
  72. use super::*;
  73. #[test]
  74. fn maps_statuses() {
  75. let di = r#"{"error":{"message":"Invalid API key","type":"invalid_request_error","code":"invalid_api_key"}}"#;
  76. assert_eq!(ProviderError::from_status(401, di, None), ProviderError::InvalidKey);
  77. assert_eq!(ProviderError::from_status(402, "", None).kind(), "insufficient_balance");
  78. let r = ProviderError::from_status(429, "{}", Some(7));
  79. assert_eq!(r.to_string(), "Rate limited by the provider; try again in 7 s.");
  80. assert_eq!(
  81. ProviderError::from_status(503, r#"{"detail":"overloaded"}"#, None),
  82. ProviderError::Upstream { status: 503, message: "overloaded".into() }
  83. );
  84. assert_eq!(ProviderError::from_status(400, r#"{"error":"bad model"}"#, None).kind(), "bad_request");
  85. }
  86. }