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::{OciError, 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                OciError::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                OciError::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                OciError::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                OciError::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(OciError::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
139            .as_ref()
140            .unwrap_or(&self.tenancy_id)
141    }
142
143    /// Create Email Delivery client
144    pub async fn email_delivery(&self) -> Result<EmailDelivery> {
145        EmailDelivery::new(self.clone()).await
146    }
147
148    /// Create Object Storage client
149    pub fn object_storage(&self, namespace: impl Into<String>) -> ObjectStorage {
150        ObjectStorage::new(self, namespace)
151    }
152}
153
154/// OCI client builder
155#[derive(Default)]
156pub struct OciBuilder {
157    user_id: Option<String>,
158    tenancy_id: Option<String>,
159    region: Option<String>,
160    fingerprint: Option<String>,
161    private_key: Option<String>,
162    compartment_id: Option<String>,
163}
164
165impl OciBuilder {
166    /// Load configuration from OCI config file
167    pub fn config(mut self, path: impl AsRef<std::path::Path>) -> Result<Self> {
168        let loaded = ConfigLoader::load_from_file(path.as_ref(), Some("DEFAULT"))?;
169
170        self.user_id = Some(loaded.user_id);
171        self.tenancy_id = Some(loaded.tenancy_id);
172        self.region = Some(loaded.region);
173        self.fingerprint = Some(loaded.fingerprint);
174        self.private_key = Some(loaded.private_key);
175        
176        Ok(self)
177    }
178
179    pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
180        self.user_id = Some(user_id.into());
181        self
182    }
183
184    pub fn tenancy_id(mut self, tenancy_id: impl Into<String>) -> Self {
185        self.tenancy_id = Some(tenancy_id.into());
186        self
187    }
188
189    pub fn region(mut self, region: impl Into<String>) -> Self {
190        self.region = Some(region.into());
191        self
192    }
193
194    pub fn fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
195        self.fingerprint = Some(fingerprint.into());
196        self
197    }
198
199    pub fn private_key(mut self, private_key: impl Into<String>) -> Result<Self> {
200        let key_input = private_key.into();
201        let loaded_key = KeyLoader::load(&key_input)?;
202        self.private_key = Some(loaded_key);
203        Ok(self)
204    }
205
206    pub fn compartment_id(mut self, compartment_id: impl Into<String>) -> Self {
207        self.compartment_id = Some(compartment_id.into());
208        self
209    }
210
211    // Internal helper for optional compartment_id
212    fn compartment_id_opt(mut self, compartment_id: Option<String>) -> Self {
213        self.compartment_id = compartment_id;
214        self
215    }
216
217    pub fn build(self) -> Result<Oci> {
218        let user_id = self.user_id.ok_or_else(|| OciError::ConfigError("user_id is not set".to_string()))?;
219        let tenancy_id = self.tenancy_id.ok_or_else(|| OciError::ConfigError("tenancy_id is not set".to_string()))?;
220        let region = self.region.ok_or_else(|| OciError::ConfigError("region is not set".to_string()))?;
221        let fingerprint = self.fingerprint.ok_or_else(|| OciError::ConfigError("fingerprint is not set".to_string()))?;
222        let private_key = self.private_key.ok_or_else(|| OciError::ConfigError("private_key is not set".to_string()))?;
223
224        let signer = OciSigner::new(&user_id, &tenancy_id, &fingerprint, &private_key)?;
225        let client = Client::builder().build()?;
226
227        Ok(Oci {
228            client,
229            region,
230            tenancy_id,
231            compartment_id: self.compartment_id,
232            signer,
233        })
234    }
235}