use std::collections::BTreeMap;
use std::sync::Arc;
use chrono::{DateTime, Utc};
use parking_lot::RwLock;
use rand::Rng;
use serde::{Deserialize, Serialize};
use fakecloud_aws::arn::Arn;
use fakecloud_core::multi_account::{AccountState, MultiAccountState};
pub const DSQL_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cluster {
pub identifier: String,
pub arn: String,
pub status: String,
pub creation_time: DateTime<Utc>,
pub deletion_protection_enabled: bool,
pub multi_region_properties: Option<MultiRegionProperties>,
pub encryption_details: EncryptionDetails,
pub endpoint: String,
pub tags: BTreeMap<String, String>,
pub policy: Option<String>,
pub policy_version: u64,
#[serde(default)]
pub client_token: Option<String>,
#[serde(default)]
pub deleted_at: Option<DateTime<Utc>>,
#[serde(default)]
pub streams: BTreeMap<String, Stream>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiRegionProperties {
pub witness_region: Option<String>,
pub clusters: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionDetails {
pub encryption_type: String,
pub kms_key_arn: Option<String>,
pub encryption_status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Stream {
pub cluster_identifier: String,
pub stream_identifier: String,
pub arn: String,
pub status: String,
pub creation_time: DateTime<Utc>,
pub ordering: String,
pub format: String,
pub target_definition: serde_json::Value,
pub status_reason: Option<String>,
pub tags: BTreeMap<String, String>,
#[serde(default)]
pub client_token: Option<String>,
#[serde(default)]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DsqlState {
pub account_id: String,
pub region: String,
pub clusters: BTreeMap<String, Cluster>,
}
impl DsqlState {
pub fn new(account_id: &str, region: &str) -> Self {
Self {
account_id: account_id.to_string(),
region: region.to_string(),
clusters: BTreeMap::new(),
}
}
}
impl AccountState for DsqlState {
fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
Self::new(account_id, region)
}
}
pub type SharedDsqlState = Arc<RwLock<MultiAccountState<DsqlState>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct DsqlSnapshot {
pub schema_version: u32,
pub accounts: MultiAccountState<DsqlState>,
}
pub fn gen_id() -> String {
const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::thread_rng();
(0..26)
.map(|_| CHARS[rng.gen_range(0..CHARS.len())] as char)
.collect()
}
pub fn cluster_arn(region: &str, account_id: &str, id: &str) -> String {
Arn::new("dsql", region, account_id, &format!("cluster/{id}")).to_string()
}
pub fn stream_arn(region: &str, account_id: &str, cluster: &str, stream: &str) -> String {
Arn::new(
"dsql",
region,
account_id,
&format!("cluster/{cluster}/stream/{stream}"),
)
.to_string()
}
pub fn cluster_endpoint(id: &str, region: &str) -> String {
format!("{id}.dsql.{region}.on.aws")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gen_id_matches_aws_pattern() {
let id = gen_id();
assert_eq!(id.len(), 26);
assert!(id
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()));
}
#[test]
fn arns_have_expected_shape() {
let id = "abcdefghij0123456789klmnop";
let c = cluster_arn("us-east-1", "123456789012", id);
assert_eq!(
c,
format!("arn:aws:dsql:us-east-1:123456789012:cluster/{id}")
);
let s = stream_arn("us-east-1", "123456789012", id, id);
assert_eq!(
s,
format!("arn:aws:dsql:us-east-1:123456789012:cluster/{id}/stream/{id}")
);
}
#[test]
fn endpoint_shape() {
assert_eq!(
cluster_endpoint("abcdefghij0123456789klmnop", "us-west-2"),
"abcdefghij0123456789klmnop.dsql.us-west-2.on.aws"
);
}
}