use std::collections::BTreeMap;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use fakecloud_core::multi_account::{AccountState, MultiAccountState};
use crate::shared::now_epoch;
pub const TRANSLATE_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TranslateData {
#[serde(default)]
pub account_id: String,
#[serde(default)]
pub region: String,
#[serde(default)]
pub terminologies: BTreeMap<String, Value>,
#[serde(default)]
pub parallel_data: BTreeMap<String, Value>,
#[serde(default)]
pub text_translation_jobs: BTreeMap<String, Value>,
#[serde(default)]
pub tags: BTreeMap<String, BTreeMap<String, String>>,
}
impl AccountState for TranslateData {
fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
Self {
account_id: account_id.to_string(),
region: region.to_string(),
..Default::default()
}
}
}
pub type SharedTranslateState = Arc<RwLock<MultiAccountState<TranslateData>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct TranslateSnapshot {
pub schema_version: u32,
pub accounts: MultiAccountState<TranslateData>,
}
impl TranslateData {
pub fn reconcile(&mut self) -> bool {
let mut changed = false;
let now = now_epoch();
for job in self.text_translation_jobs.values_mut() {
let Some(obj) = job.as_object_mut() else {
continue;
};
match obj.get("JobStatus").and_then(Value::as_str) {
Some("SUBMITTED") | Some("IN_PROGRESS") => {
obj.insert("JobStatus".into(), json!("COMPLETED"));
obj.insert("EndTime".into(), json!(now));
obj.entry("JobDetails").or_insert(json!({
"TranslatedDocumentsCount": 0,
"DocumentsWithErrorsCount": 0,
"InputDocumentsCount": 0,
}));
changed = true;
}
Some("STOP_REQUESTED") => {
obj.insert("JobStatus".into(), json!("STOPPED"));
obj.insert("EndTime".into(), json!(now));
changed = true;
}
_ => {}
}
}
for pd in self.parallel_data.values_mut() {
let Some(obj) = pd.as_object_mut() else {
continue;
};
if obj.get("Status").and_then(Value::as_str) == Some("CREATING") {
obj.insert("Status".into(), json!("ACTIVE"));
obj.insert("LastUpdatedAt".into(), json!(now));
changed = true;
}
if obj.get("LatestUpdateAttemptStatus").and_then(Value::as_str) == Some("UPDATING") {
obj.insert("LatestUpdateAttemptStatus".into(), json!("ACTIVE"));
obj.insert("Status".into(), json!("ACTIVE"));
obj.insert("LastUpdatedAt".into(), json!(now));
changed = true;
}
}
changed
}
}
#[cfg(test)]
mod tests {
use super::*;
fn data() -> TranslateData {
TranslateData::new_for_account("000000000000", "us-east-1", "")
}
#[test]
fn job_settles_to_completed() {
let mut d = data();
d.text_translation_jobs.insert(
"j1".into(),
json!({ "JobId": "j1", "JobStatus": "SUBMITTED" }),
);
assert!(d.reconcile());
let j = &d.text_translation_jobs["j1"];
assert_eq!(j["JobStatus"], json!("COMPLETED"));
assert!(j.get("EndTime").is_some());
assert!(j.get("JobDetails").is_some());
assert!(!d.reconcile());
}
#[test]
fn stop_requested_settles_to_stopped() {
let mut d = data();
d.text_translation_jobs.insert(
"j2".into(),
json!({ "JobId": "j2", "JobStatus": "STOP_REQUESTED" }),
);
assert!(d.reconcile());
assert_eq!(d.text_translation_jobs["j2"]["JobStatus"], json!("STOPPED"));
}
#[test]
fn parallel_data_settles_to_active() {
let mut d = data();
d.parallel_data
.insert("p1".into(), json!({ "Name": "p1", "Status": "CREATING" }));
assert!(d.reconcile());
assert_eq!(d.parallel_data["p1"]["Status"], json!("ACTIVE"));
assert!(!d.reconcile());
}
#[test]
fn parallel_data_update_settles() {
let mut d = data();
d.parallel_data.insert(
"p2".into(),
json!({ "Name": "p2", "Status": "ACTIVE", "LatestUpdateAttemptStatus": "UPDATING" }),
);
assert!(d.reconcile());
assert_eq!(
d.parallel_data["p2"]["LatestUpdateAttemptStatus"],
json!("ACTIVE")
);
}
}