fakecloud_translate/
state.rs1use std::collections::BTreeMap;
17use std::sync::Arc;
18
19use parking_lot::RwLock;
20use serde::{Deserialize, Serialize};
21use serde_json::{json, Value};
22
23use fakecloud_core::multi_account::{AccountState, MultiAccountState};
24
25use crate::shared::now_epoch;
26
27pub const TRANSLATE_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
28
29#[derive(Debug, Clone, Default, Serialize, Deserialize)]
31pub struct TranslateData {
32 #[serde(default)]
34 pub account_id: String,
35 #[serde(default)]
37 pub region: String,
38
39 #[serde(default)]
41 pub terminologies: BTreeMap<String, Value>,
42 #[serde(default)]
44 pub parallel_data: BTreeMap<String, Value>,
45 #[serde(default)]
48 pub text_translation_jobs: BTreeMap<String, Value>,
49
50 #[serde(default)]
52 pub tags: BTreeMap<String, BTreeMap<String, String>>,
53}
54
55impl AccountState for TranslateData {
56 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
57 Self {
58 account_id: account_id.to_string(),
59 region: region.to_string(),
60 ..Default::default()
61 }
62 }
63}
64
65pub type SharedTranslateState = Arc<RwLock<MultiAccountState<TranslateData>>>;
66
67#[derive(Debug, Serialize, Deserialize)]
68pub struct TranslateSnapshot {
69 pub schema_version: u32,
70 pub accounts: MultiAccountState<TranslateData>,
71}
72
73impl TranslateData {
74 pub fn reconcile(&mut self) -> bool {
78 let mut changed = false;
79 let now = now_epoch();
80
81 for job in self.text_translation_jobs.values_mut() {
83 let Some(obj) = job.as_object_mut() else {
84 continue;
85 };
86 match obj.get("JobStatus").and_then(Value::as_str) {
87 Some("SUBMITTED") | Some("IN_PROGRESS") => {
88 obj.insert("JobStatus".into(), json!("COMPLETED"));
89 obj.insert("EndTime".into(), json!(now));
90 obj.entry("JobDetails").or_insert(json!({
91 "TranslatedDocumentsCount": 0,
92 "DocumentsWithErrorsCount": 0,
93 "InputDocumentsCount": 0,
94 }));
95 changed = true;
96 }
97 Some("STOP_REQUESTED") => {
98 obj.insert("JobStatus".into(), json!("STOPPED"));
99 obj.insert("EndTime".into(), json!(now));
100 changed = true;
101 }
102 _ => {}
103 }
104 }
105
106 for pd in self.parallel_data.values_mut() {
109 let Some(obj) = pd.as_object_mut() else {
110 continue;
111 };
112 if obj.get("Status").and_then(Value::as_str) == Some("CREATING") {
113 obj.insert("Status".into(), json!("ACTIVE"));
114 obj.insert("LastUpdatedAt".into(), json!(now));
115 changed = true;
116 }
117 if obj.get("LatestUpdateAttemptStatus").and_then(Value::as_str) == Some("UPDATING") {
118 obj.insert("LatestUpdateAttemptStatus".into(), json!("ACTIVE"));
119 obj.insert("Status".into(), json!("ACTIVE"));
120 obj.insert("LastUpdatedAt".into(), json!(now));
121 changed = true;
122 }
123 }
124
125 changed
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn data() -> TranslateData {
134 TranslateData::new_for_account("000000000000", "us-east-1", "")
135 }
136
137 #[test]
138 fn job_settles_to_completed() {
139 let mut d = data();
140 d.text_translation_jobs.insert(
141 "j1".into(),
142 json!({ "JobId": "j1", "JobStatus": "SUBMITTED" }),
143 );
144 assert!(d.reconcile());
145 let j = &d.text_translation_jobs["j1"];
146 assert_eq!(j["JobStatus"], json!("COMPLETED"));
147 assert!(j.get("EndTime").is_some());
148 assert!(j.get("JobDetails").is_some());
149 assert!(!d.reconcile());
150 }
151
152 #[test]
153 fn stop_requested_settles_to_stopped() {
154 let mut d = data();
155 d.text_translation_jobs.insert(
156 "j2".into(),
157 json!({ "JobId": "j2", "JobStatus": "STOP_REQUESTED" }),
158 );
159 assert!(d.reconcile());
160 assert_eq!(d.text_translation_jobs["j2"]["JobStatus"], json!("STOPPED"));
161 }
162
163 #[test]
164 fn parallel_data_settles_to_active() {
165 let mut d = data();
166 d.parallel_data
167 .insert("p1".into(), json!({ "Name": "p1", "Status": "CREATING" }));
168 assert!(d.reconcile());
169 assert_eq!(d.parallel_data["p1"]["Status"], json!("ACTIVE"));
170 assert!(!d.reconcile());
171 }
172
173 #[test]
174 fn parallel_data_update_settles() {
175 let mut d = data();
176 d.parallel_data.insert(
177 "p2".into(),
178 json!({ "Name": "p2", "Status": "ACTIVE", "LatestUpdateAttemptStatus": "UPDATING" }),
179 );
180 assert!(d.reconcile());
181 assert_eq!(
182 d.parallel_data["p2"]["LatestUpdateAttemptStatus"],
183 json!("ACTIVE")
184 );
185 }
186}