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