wami/
types.rs

1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::Value;
3
4/// Common response wrapper for AWS operations
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct AmiResponse<T> {
7    pub success: bool,
8    pub data: Option<T>,
9    pub error: Option<String>,
10}
11
12impl<T> AmiResponse<T> {
13    pub fn success(data: T) -> Self {
14        Self {
15            success: true,
16            data: Some(data),
17            error: None,
18        }
19    }
20
21    pub fn error(error: String) -> Self {
22        Self {
23            success: false,
24            data: None,
25            error: Some(error),
26        }
27    }
28}
29
30/// AWS region configuration
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AwsConfig {
33    pub region: String,
34    pub profile: Option<String>,
35    pub account_id: String,
36}
37
38impl Default for AwsConfig {
39    fn default() -> Self {
40        Self {
41            region: "us-east-1".to_string(),
42            profile: None,
43            account_id: Self::generate_account_id(),
44        }
45    }
46}
47
48impl AwsConfig {
49    /// Generate a random AWS account ID (12 digits)
50    pub fn generate_account_id() -> String {
51        use std::collections::hash_map::DefaultHasher;
52        use std::hash::{Hash, Hasher};
53
54        let mut hasher = DefaultHasher::new();
55        chrono::Utc::now()
56            .timestamp_nanos_opt()
57            .unwrap_or(0)
58            .hash(&mut hasher);
59        let hash = hasher.finish();
60
61        // Generate 12-digit account ID
62        format!("{:012}", hash % 1_000_000_000_000)
63    }
64
65    /// Create a new config with a specific account ID
66    pub fn with_account_id(account_id: String) -> Self {
67        Self {
68            region: "us-east-1".to_string(),
69            profile: None,
70            account_id,
71        }
72    }
73}
74
75/// Common pagination parameters
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct PaginationParams {
78    pub max_items: Option<i32>,
79    pub marker: Option<String>,
80}
81
82/// Tag representation
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct Tag {
85    pub key: String,
86    pub value: String,
87}
88
89/// Policy document representation
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct PolicyDocument {
92    #[serde(rename = "Version")]
93    pub version: String,
94    #[serde(rename = "Statement")]
95    pub statement: Vec<PolicyStatement>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct PolicyStatement {
100    #[serde(rename = "Effect")]
101    pub effect: String,
102    #[serde(rename = "Action", deserialize_with = "string_or_vec")]
103    pub action: Vec<String>,
104    #[serde(rename = "Resource", deserialize_with = "string_or_vec")]
105    pub resource: Vec<String>,
106    #[serde(rename = "Condition", skip_serializing_if = "Option::is_none")]
107    pub condition: Option<Value>,
108}
109
110/// Deserialize either a single string or an array of strings into a Vec<String>
111fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
112where
113    D: Deserializer<'de>,
114{
115    let value = Value::deserialize(deserializer)?;
116    match value {
117        Value::String(s) => Ok(vec![s]),
118        Value::Array(arr) => arr
119            .into_iter()
120            .map(|v| {
121                v.as_str()
122                    .map(String::from)
123                    .ok_or_else(|| serde::de::Error::custom("expected string"))
124            })
125            .collect(),
126        _ => Err(serde::de::Error::custom(
127            "expected string or array of strings",
128        )),
129    }
130}