oci_api/client/
http.rs

1//! OCI HTTP client
2//!
3//! OCI API HTTP client with custom request signing
4
5use crate::auth::config_loader::ConfigLoader;
6use crate::auth::key_loader::KeyLoader;
7use crate::client::signer::OciSigner;
8use crate::error::{Error, Result};
9use crate::services::email::EmailDelivery;
10use crate::services::object_storage::ObjectStorage;
11use reqwest::Client;
12use std::env;
13
14/// OCI HTTP client
15#[derive(Clone)]
16pub struct Oci {
17    /// HTTP client
18    client: Client,
19
20    /// Region
21    region: String,
22
23    /// Tenancy ID
24    tenancy_id: String,
25
26    /// Compartment ID
27    compartment_id: Option<String>,
28
29    /// Request signer
30    signer: OciSigner,
31}
32
33impl Default for Oci {
34    fn default() -> Self {
35        Self::from_env().expect("Failed to create OCI client from environment")
36    }
37}
38
39impl Oci {
40    /// Create new OCI client from environment variables
41    pub fn from_env() -> Result<Self> {
42        // Step 1: Load partial configuration from OCI_CONFIG if available
43        let partial_config = if let Ok(config_value) = env::var("OCI_CONFIG") {
44            Some(ConfigLoader::load_partial_from_env_var(&config_value)?)
45        } else {
46            None
47        };
48
49        // Step 2: Merge with individual environment variables (highest priority)
50        let user_id = env::var("OCI_USER_ID")
51            .ok()
52            .or_else(|| partial_config.as_ref().and_then(|c| c.user_id.clone()))
53            .ok_or_else(|| {
54                Error::EnvError(
55                    "OCI_USER_ID must be set (either directly or via OCI_CONFIG)".to_string(),
56                )
57            })?;
58
59        let tenancy_id = env::var("OCI_TENANCY_ID")
60            .ok()
61            .or_else(|| partial_config.as_ref().and_then(|c| c.tenancy_id.clone()))
62            .ok_or_else(|| {
63                Error::EnvError(
64                    "OCI_TENANCY_ID must be set (either directly or via OCI_CONFIG)".to_string(),
65                )
66            })?;
67
68        let region = env::var("OCI_REGION")
69            .ok()
70            .or_else(|| partial_config.as_ref().and_then(|c| c.region.clone()))
71            .ok_or_else(|| {
72                Error::EnvError(
73                    "OCI_REGION must be set (either directly or via OCI_CONFIG)".to_string(),
74                )
75            })?;
76
77        let fingerprint = env::var("OCI_FINGERPRINT")
78            .ok()
79            .or_else(|| partial_config.as_ref().and_then(|c| c.fingerprint.clone()))
80            .ok_or_else(|| {
81                Error::EnvError(
82                    "OCI_FINGERPRINT must be set (either directly or via OCI_CONFIG)".to_string(),
83                )
84            })?;
85
86        // Step 3: Load private key
87        let private_key = if let Ok(key_input) = env::var("OCI_PRIVATE_KEY") {
88            KeyLoader::load(&key_input)?
89        } else if let Ok(config_value) = env::var("OCI_CONFIG") {
90            let full_config = ConfigLoader::load_from_env_var(&config_value, None)?;
91            full_config.private_key
92        } else {
93            return Err(Error::EnvError(
94                "OCI_PRIVATE_KEY must be set (or key_file must be in OCI_CONFIG)".to_string(),
95            ));
96        };
97
98        // Step 4: Optional compartment ID
99        let compartment_id = env::var("OCI_COMPARTMENT_ID").ok();
100
101        Self::builder()
102            .user_id(user_id)
103            .tenancy_id(tenancy_id)
104            .region(region)
105            .fingerprint(fingerprint)
106            .private_key(private_key)?
107            .compartment_id_opt(compartment_id)
108            .build()
109    }
110
111    /// Start builder pattern
112    pub fn builder() -> OciBuilder {
113        OciBuilder::default()
114    }
115
116    /// Get request signer
117    pub fn signer(&self) -> &OciSigner {
118        &self.signer
119    }
120
121    /// Return HTTP client reference
122    pub fn client(&self) -> &Client {
123        &self.client
124    }
125
126    /// Return region
127    pub fn region(&self) -> &str {
128        &self.region
129    }
130
131    /// Return tenancy ID
132    pub fn tenancy_id(&self) -> &str {
133        &self.tenancy_id
134    }
135
136    /// Return compartment ID (defaults to tenancy_id if not set)
137    pub fn compartment_id(&self) -> &str {
138        self.compartment_id.as_ref().unwrap_or(&self.tenancy_id)
139    }
140
141    /// Create Email Delivery client
142    pub async fn email_delivery(&self) -> Result<EmailDelivery> {
143        EmailDelivery::new(self.clone()).await
144    }
145
146    /// Create Object Storage client
147    pub fn object_storage(&self, namespace: impl Into<String>) -> ObjectStorage {
148        ObjectStorage::new(self, namespace)
149    }
150}
151
152/// OCI client builder
153#[derive(Default)]
154pub struct OciBuilder {
155    user_id: Option<String>,
156    tenancy_id: Option<String>,
157    region: Option<String>,
158    fingerprint: Option<String>,
159    private_key: Option<String>,
160    compartment_id: Option<String>,
161}
162
163impl OciBuilder {
164    /// Load configuration from OCI config file
165    pub fn config(mut self, path: impl AsRef<std::path::Path>) -> Result<Self> {
166        let loaded = ConfigLoader::load_from_file(path.as_ref(), Some("DEFAULT"))?;
167
168        self.user_id = Some(loaded.user_id);
169        self.tenancy_id = Some(loaded.tenancy_id);
170        self.region = Some(loaded.region);
171        self.fingerprint = Some(loaded.fingerprint);
172        self.private_key = Some(loaded.private_key);
173
174        Ok(self)
175    }
176
177    pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
178        self.user_id = Some(user_id.into());
179        self
180    }
181
182    pub fn tenancy_id(mut self, tenancy_id: impl Into<String>) -> Self {
183        self.tenancy_id = Some(tenancy_id.into());
184        self
185    }
186
187    pub fn region(mut self, region: impl Into<String>) -> Self {
188        self.region = Some(region.into());
189        self
190    }
191
192    pub fn fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
193        self.fingerprint = Some(fingerprint.into());
194        self
195    }
196
197    pub fn private_key(mut self, private_key: impl Into<String>) -> Result<Self> {
198        let key_input = private_key.into();
199        let loaded_key = KeyLoader::load(&key_input)?;
200        self.private_key = Some(loaded_key);
201        Ok(self)
202    }
203
204    pub fn compartment_id(mut self, compartment_id: impl Into<String>) -> Self {
205        self.compartment_id = Some(compartment_id.into());
206        self
207    }
208
209    // Internal helper for optional compartment_id
210    fn compartment_id_opt(mut self, compartment_id: Option<String>) -> Self {
211        self.compartment_id = compartment_id;
212        self
213    }
214
215    pub fn build(self) -> Result<Oci> {
216        let user_id = self
217            .user_id
218            .ok_or_else(|| Error::ConfigError("user_id is not set".to_string()))?;
219        let tenancy_id = self
220            .tenancy_id
221            .ok_or_else(|| Error::ConfigError("tenancy_id is not set".to_string()))?;
222        let region = self
223            .region
224            .ok_or_else(|| Error::ConfigError("region is not set".to_string()))?;
225        let fingerprint = self
226            .fingerprint
227            .ok_or_else(|| Error::ConfigError("fingerprint is not set".to_string()))?;
228        let private_key = self
229            .private_key
230            .ok_or_else(|| Error::ConfigError("private_key is not set".to_string()))?;
231
232        let signer = OciSigner::new(&user_id, &tenancy_id, &fingerprint, &private_key)?;
233        let client = Client::builder().build()?;
234
235        Ok(Oci {
236            client,
237            region,
238            tenancy_id,
239            compartment_id: self.compartment_id,
240            signer,
241        })
242    }
243}