1use 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#[derive(Clone)]
16pub struct Oci {
17 client: Client,
19
20 region: String,
22
23 tenancy_id: String,
25
26 compartment_id: Option<String>,
28
29 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 pub fn from_env() -> Result<Self> {
42 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 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 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 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 pub fn builder() -> OciBuilder {
113 OciBuilder::default()
114 }
115
116 pub fn signer(&self) -> &OciSigner {
118 &self.signer
119 }
120
121 pub fn client(&self) -> &Client {
123 &self.client
124 }
125
126 pub fn region(&self) -> &str {
128 &self.region
129 }
130
131 pub fn tenancy_id(&self) -> &str {
133 &self.tenancy_id
134 }
135
136 pub fn compartment_id(&self) -> &str {
138 self.compartment_id
139 .as_ref()
140 .unwrap_or(&self.tenancy_id)
141 }
142
143 pub async fn email_delivery(&self) -> Result<EmailDelivery> {
145 EmailDelivery::new(self.clone()).await
146 }
147
148 pub fn object_storage(&self, namespace: impl Into<String>) -> ObjectStorage {
150 ObjectStorage::new(self, namespace)
151 }
152}
153
154#[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 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 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}