1use crate::Error;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5pub struct ProviderOutcome {
6 pub provider: String,
7 pub status: ProviderStatus,
8 #[serde(skip_serializing_if = "Option::is_none")]
9 pub message: Option<String>,
10}
11
12#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "snake_case")]
14pub enum ProviderStatus {
15 Success,
16 Unavailable,
17 RateLimited,
18 Failed,
19}
20
21impl ProviderOutcome {
22 pub fn success(provider: impl Into<String>) -> Self {
23 Self {
24 provider: provider.into(),
25 status: ProviderStatus::Success,
26 message: None,
27 }
28 }
29
30 pub fn from_error(provider: impl Into<String>, error: &Error) -> Self {
31 let status = match error {
32 Error::Reqwest(error)
33 if error.status() == Some(reqwest::StatusCode::TOO_MANY_REQUESTS) =>
34 {
35 ProviderStatus::RateLimited
36 }
37 Error::Reqwest(error) if error.is_timeout() || error.is_connect() => {
38 ProviderStatus::Unavailable
39 }
40 _ => ProviderStatus::Failed,
41 };
42 Self {
43 provider: provider.into(),
44 status,
45 message: Some(error.to_string()),
46 }
47 }
48
49 pub fn succeeded(&self) -> bool {
50 self.status == ProviderStatus::Success
51 }
52
53 pub fn aggregate(provider: impl Into<String>, outcomes: &[Self]) -> Self {
54 let status = outcomes
55 .iter()
56 .map(|outcome| outcome.status)
57 .max_by_key(|status| match status {
58 ProviderStatus::Success => 0,
59 ProviderStatus::Unavailable => 1,
60 ProviderStatus::RateLimited => 2,
61 ProviderStatus::Failed => 3,
62 })
63 .unwrap_or(ProviderStatus::Success);
64 let messages: Vec<_> = outcomes
65 .iter()
66 .filter_map(|outcome| outcome.message.as_deref())
67 .collect();
68 Self {
69 provider: provider.into(),
70 status,
71 message: (!messages.is_empty()).then(|| messages.join("; ")),
72 }
73 }
74}
75
76pub fn default_success() -> ProviderOutcome {
77 ProviderOutcome::success("legacy")
78}