oci_api/services/email/
client.rs

1//! Email client
2
3use crate::client::OciClient;
4use crate::error::{OciError, Result};
5use crate::services::email::models::*;
6
7/// Email client
8#[derive(Clone)]
9pub struct EmailClient {
10    /// OCI HTTP client
11    oci_client: OciClient,
12
13    /// Submit endpoint (loaded from email configuration)
14    submit_endpoint: String,
15}
16
17impl EmailClient {
18    /// Create new Email client
19    ///
20    /// Loads email configuration and caches the submit endpoint.
21    ///
22    /// # Arguments
23    /// * `oci_client` - OCI HTTP client
24    pub async fn new(oci_client: OciClient) -> Result<Self> {
25        let compartment_id = oci_client.compartment_id().to_string();
26        let region = oci_client.region().to_string();
27
28        // Get email configuration
29        let config =
30            Self::get_email_configuration_internal(&oci_client, &compartment_id, &region).await?;
31
32        Ok(Self {
33            oci_client,
34            submit_endpoint: config.http_submit_endpoint,
35        })
36    }
37
38    /// Get Email Configuration (internal helper)
39    async fn get_email_configuration_internal(
40        oci_client: &OciClient,
41        compartment_id: &str,
42        region: &str,
43    ) -> Result<EmailConfiguration> {
44        // Build path with query string
45        let path = format!("/20170907/configuration?compartmentId={}", compartment_id);
46        let host = format!("ctrl.email.{}.oci.oraclecloud.com", region);
47        let url = format!("https://{}{}", host, path);
48
49        // Sign request
50        let (date_header, auth_header) = oci_client
51            .signer()
52            .sign_request("GET", &path, &host, None)?;
53
54        // Build and execute request
55        let response = oci_client
56            .client()
57            .get(&url)
58            .header("host", &host)
59            .header("date", &date_header)
60            .header("authorization", &auth_header)
61            .send()
62            .await?;
63
64        if !response.status().is_success() {
65            let status = response.status();
66            let body = response.text().await?;
67            return Err(OciError::ApiError {
68                code: status.to_string(),
69                message: body,
70            });
71        }
72
73        response.json().await.map_err(Into::into)
74    }
75
76    /// Get Email Configuration (public API)
77    ///
78    /// # Arguments
79    /// * `compartment_id` - Compartment OCID (typically tenancy OCID)
80    pub async fn get_email_configuration(
81        &self,
82        compartment_id: impl Into<String>,
83    ) -> Result<EmailConfiguration> {
84        let compartment_id = compartment_id.into();
85        let region = self.oci_client.region().to_string();
86        Self::get_email_configuration_internal(&self.oci_client, &compartment_id, &region).await
87    }
88
89    /// Send email
90    ///
91    /// # Arguments
92    /// * `email` - Email message
93    ///
94    /// # Note
95    /// The compartment_id from OciClient will be automatically set in the sender.
96    pub async fn send(&self, mut email: Email) -> Result<SubmitEmailResponse> {
97        // Get compartment_id from OciClient
98        let compartment_id = self.oci_client.compartment_id().to_string();
99
100        // Set compartment_id in sender if not already set
101        if email.sender.compartment_id.is_empty() {
102            email.sender.set_compartment_id(&compartment_id);
103        }
104
105        // Build path and URL
106        let path = "/20220926/actions/submitEmail";
107        let url = format!("https://{}{}", &self.submit_endpoint, path);
108
109        // Serialize JSON body
110        let body_json = serde_json::to_string(&email)?;
111
112        // Calculate body SHA256 for x-content-sha256 header
113        let body_sha256 = {
114            use base64::{Engine, engine::general_purpose};
115            use sha2::{Digest, Sha256};
116            let mut hasher = Sha256::new();
117            hasher.update(body_json.as_bytes());
118            let result = hasher.finalize();
119            general_purpose::STANDARD.encode(result)
120        };
121
122        // Sign request (with body)
123        let (date_header, auth_header) = self.oci_client.signer().sign_request(
124            "POST",
125            path,
126            &self.submit_endpoint,
127            Some(&body_json),
128        )?;
129
130        // Build and execute request
131        let response = self
132            .oci_client
133            .client()
134            .post(&url)
135            .header("host", &self.submit_endpoint)
136            .header("date", &date_header)
137            .header("authorization", &auth_header)
138            .header("content-type", "application/json")
139            .header("content-length", body_json.len().to_string())
140            .header("x-content-sha256", &body_sha256)
141            .body(body_json)
142            .send()
143            .await?;
144
145        if !response.status().is_success() {
146            let status = response.status();
147            let body = response.text().await?;
148            return Err(OciError::ApiError {
149                code: status.to_string(),
150                message: body,
151            });
152        }
153
154        let submit_response: SubmitEmailResponse = response.json().await?;
155        Ok(submit_response)
156    }
157
158    /// List approved senders
159    ///
160    /// # Arguments
161    /// * `compartment_id` - Compartment OCID (required)
162    /// * `lifecycle_state` - Optional filter by lifecycle state
163    /// * `email_address` - Optional filter by email address
164    pub async fn list_senders(
165        &self,
166        compartment_id: impl Into<String>,
167        lifecycle_state: Option<&str>,
168        email_address: Option<&str>,
169    ) -> Result<Vec<SenderSummary>> {
170        let compartment_id = compartment_id.into();
171
172        // Build query string
173        let mut query_params = vec![format!("compartmentId={}", compartment_id)];
174
175        if let Some(state) = lifecycle_state {
176            query_params.push(format!("lifecycleState={}", state));
177        }
178
179        if let Some(email) = email_address {
180            query_params.push(format!("emailAddress={}", email));
181        }
182
183        let query_string = query_params.join("&");
184        let path = format!("/20170907/senders?{}", query_string);
185        let host = format!(
186            "ctrl.email.{}.oci.oraclecloud.com",
187            self.oci_client.region()
188        );
189        let url = format!("https://{}{}", host, path);
190
191        // Sign request
192        let (date_header, auth_header) = self
193            .oci_client
194            .signer()
195            .sign_request("GET", &path, &host, None)?;
196
197        // Build and execute request
198        let response = self
199            .oci_client
200            .client()
201            .get(&url)
202            .header("host", &host)
203            .header("date", &date_header)
204            .header("authorization", &auth_header)
205            .send()
206            .await?;
207
208        if !response.status().is_success() {
209            let status = response.status();
210            let body = response.text().await?;
211            return Err(OciError::ApiError {
212                code: status.to_string(),
213                message: body,
214            });
215        }
216
217        let senders: Vec<SenderSummary> = response.json().await?;
218        Ok(senders)
219    }
220}