Skip to main content

winterbaume_support/
state.rs

1use std::collections::HashMap;
2
3use chrono::Utc;
4use thiserror::Error;
5use uuid::Uuid;
6
7use crate::types::*;
8
9const TRUSTED_ADVISOR_STATUSES: &[&str] =
10    &["none", "enqueued", "processing", "success", "abandoned"];
11
12#[derive(Debug, Default)]
13pub struct SupportState {
14    pub cases: HashMap<String, SupportCase>,
15    next_display_id: u64,
16    /// Tracks how many times each check ID has been refreshed, for cycling statuses.
17    pub refresh_call_counts: HashMap<String, usize>,
18}
19
20impl SupportState {
21    pub fn next_refresh_status(&mut self, check_id: &str) -> &str {
22        let count = self
23            .refresh_call_counts
24            .entry(check_id.to_string())
25            .or_insert(0);
26        let status = TRUSTED_ADVISOR_STATUSES[*count % TRUSTED_ADVISOR_STATUSES.len()];
27        *count += 1;
28        status
29    }
30}
31
32#[derive(Debug, Error)]
33pub enum SupportError {
34    #[error("The requested CaseId could not be located.")]
35    CaseIdNotFound,
36}
37
38impl SupportState {
39    pub fn create_case(
40        &mut self,
41        subject: &str,
42        communication_body: &str,
43        service_code: Option<&str>,
44        severity_code: Option<&str>,
45        category_code: Option<&str>,
46        cc_email_addresses: Vec<String>,
47        language: Option<&str>,
48    ) -> Result<&SupportCase, SupportError> {
49        let case_uuid = Uuid::new_v4().to_string().replace('-', "");
50        let case_id = format!(
51            "case-{}-{}-{}",
52            &case_uuid[..12],
53            Utc::now().format("%Y"),
54            &case_uuid[12..27]
55        );
56
57        self.next_display_id += 1;
58        let display_id = format!("{}", self.next_display_id);
59
60        let support_case = SupportCase {
61            case_id: case_id.clone(),
62            display_id,
63            subject: subject.to_string(),
64            status: "opened".to_string(),
65            service_code: service_code.unwrap_or("general-info").to_string(),
66            category_code: category_code.unwrap_or("other").to_string(),
67            severity_code: severity_code.unwrap_or("normal").to_string(),
68            communication_body: communication_body.to_string(),
69            submitted_by: "user@example.com".to_string(),
70            time_created: Utc::now().to_rfc3339(),
71            cc_email_addresses,
72            language: language.unwrap_or("en").to_string(),
73        };
74
75        self.cases.insert(case_id.clone(), support_case);
76        Ok(self.cases.get(&case_id).unwrap())
77    }
78
79    pub fn describe_cases(
80        &self,
81        case_id_list: Option<&[String]>,
82        include_resolved: bool,
83    ) -> Vec<&SupportCase> {
84        self.cases
85            .values()
86            .filter(|c| {
87                if !include_resolved && c.status == "resolved" {
88                    return false;
89                }
90                if let Some(ids) = case_id_list
91                    && !ids.is_empty()
92                {
93                    return ids.iter().any(|id| id == &c.case_id);
94                }
95                true
96            })
97            .collect()
98    }
99
100    pub fn resolve_case(
101        &mut self,
102        case_id: Option<&str>,
103    ) -> Result<(String, String), SupportError> {
104        let case_id = match case_id {
105            Some(id) => id.to_string(),
106            None => {
107                // If no caseId, resolve the most recent case
108                match self
109                    .cases
110                    .values()
111                    .filter(|c| c.status != "resolved")
112                    .last()
113                {
114                    Some(c) => c.case_id.clone(),
115                    None => {
116                        return Err(SupportError::CaseIdNotFound);
117                    }
118                }
119            }
120        };
121
122        match self.cases.get_mut(&case_id) {
123            Some(c) => {
124                let initial_status = c.status.clone();
125                c.status = "resolved".to_string();
126                Ok((initial_status, "resolved".to_string()))
127            }
128            None => Err(SupportError::CaseIdNotFound),
129        }
130    }
131
132    pub fn describe_services(&self, service_code_list: Option<&[String]>) -> Vec<SupportService> {
133        let all_services = default_services();
134        match service_code_list {
135            Some(codes) if !codes.is_empty() => all_services
136                .into_iter()
137                .filter(|s| codes.iter().any(|c| c == &s.code))
138                .collect(),
139            _ => all_services,
140        }
141    }
142}
143
144fn default_services() -> Vec<SupportService> {
145    vec![
146        SupportService {
147            code: "amazon-elastic-compute-cloud-linux".to_string(),
148            name: "Amazon Elastic Compute Cloud (Linux)".to_string(),
149            categories: vec![
150                SupportCategory {
151                    code: "general-info".to_string(),
152                    name: "General Info".to_string(),
153                },
154                SupportCategory {
155                    code: "instance-issue".to_string(),
156                    name: "Instance Issue".to_string(),
157                },
158            ],
159        },
160        SupportService {
161            code: "amazon-simple-storage-service".to_string(),
162            name: "Amazon Simple Storage Service".to_string(),
163            categories: vec![SupportCategory {
164                code: "general-info".to_string(),
165                name: "General Info".to_string(),
166            }],
167        },
168        SupportService {
169            code: "general-info".to_string(),
170            name: "General Info".to_string(),
171            categories: vec![SupportCategory {
172                code: "other".to_string(),
173                name: "Other".to_string(),
174            }],
175        },
176    ]
177}