cronback_api_model/
attempt.rs

1use std::time::Duration;
2
3use chrono::{DateTime, Utc};
4#[cfg(feature = "dto")]
5use dto::FromProto;
6use serde::{Deserialize, Serialize};
7use serde_with::{serde_as, skip_serializing_none, DurationSecondsWithFrac};
8use strum::Display;
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
11#[cfg_attr(
12    feature = "dto",
13    derive(FromProto),
14    proto(target = "proto::attempt_proto::Attempt")
15)]
16#[skip_serializing_none]
17pub struct Attempt {
18    pub status: AttemptStatus,
19    #[cfg_attr(feature = "dto", proto(required))]
20    pub details: AttemptDetails,
21    pub attempt_num: u32,
22    #[cfg_attr(feature = "dto", proto(required))]
23    pub created_at: DateTime<Utc>,
24}
25
26#[serde_as]
27#[skip_serializing_none]
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29#[cfg_attr(
30    feature = "dto",
31    derive(FromProto),
32    proto(target = "proto::attempt_proto::WebhookAttemptDetails")
33)]
34pub struct WebhookAttemptDetails {
35    pub response_code: Option<i32>,
36    #[cfg_attr(feature = "dto", from_proto(map = "Duration::from_secs_f64"))]
37    #[serde_as(as = "DurationSecondsWithFrac")]
38    pub response_latency_s: Duration,
39    pub error_message: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
43#[cfg_attr(feature = "client", non_exhaustive)]
44#[cfg_attr(
45    feature = "dto",
46    derive(FromProto),
47    proto(target = "proto::attempt_proto::AttemptDetails", oneof = "details")
48)]
49#[serde(untagged)]
50pub enum AttemptDetails {
51    #[cfg_attr(feature = "dto", proto(name = "Webhook"))]
52    WebhookAttemptDetails(WebhookAttemptDetails),
53}
54
55impl AttemptDetails {
56    pub fn status_message(&self) -> String {
57        match self {
58            | Self::WebhookAttemptDetails(details) => {
59                format!(
60                    "{}{}",
61                    details
62                        .response_code
63                        .map(|a| format!("{} ", a))
64                        .unwrap_or_default(),
65                    details.error_message.as_deref().unwrap_or_default()
66                )
67            }
68        }
69    }
70}
71
72#[non_exhaustive]
73#[derive(Debug, Display, Clone, Copy, Serialize, Deserialize, PartialEq)]
74#[cfg_attr(
75    feature = "dto",
76    derive(FromProto),
77    proto(target = "proto::attempt_proto::AttemptStatus")
78)]
79#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
80#[cfg_attr(feature = "clap", clap(rename_all = "snake_case"))]
81#[serde(rename_all = "snake_case")]
82// It's unfortunate that strum won't read serde's rename_all attribute. Maybe we
83// can contribute by addressing the [issue](https://github.com/Peternator7/strum/issues/113)
84#[strum(serialize_all = "snake_case")]
85pub enum AttemptStatus {
86    Succeeded,
87    Failed,
88}
89
90#[cfg(test)]
91mod test {
92    use crate::AttemptStatus;
93
94    #[test]
95    fn attempt_status_to_string() {
96        // swap the order of arguments of assert_eq in the next two lines
97        assert_eq!("succeeded", AttemptStatus::Succeeded.to_string());
98        assert_eq!("failed", AttemptStatus::Failed.to_string());
99    }
100}