1use reqwest::Client;
2use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Middleware};
3use std::env;
4use std::sync::Arc;
5
6use super::{ApiClient, Error, Result};
7use crate::api::core::sequences::SequencesResource;
8use crate::api::data_modeling::Models;
9use crate::api::iam::groups::GroupsResource;
10use crate::api::iam::sessions::SessionsResource;
11use crate::auth::AuthenticatorMiddleware;
12use crate::retry::CustomRetryMiddleware;
13use crate::AuthHeaderManager;
14use crate::{
15 assets::AssetsResource, datasets::DataSetsResource, events::EventsResource,
16 extpipes::ExtPipeRunsResource, extpipes::ExtPipesResource, files::Files,
17 labels::LabelsResource, raw::RawResource, relationships::RelationshipsResource,
18 time_series::TimeSeriesResource,
19};
20
21use crate::api::authenticator::{Authenticator, AuthenticatorConfig};
22
23macro_rules! env_or_error {
24 ($e: expr) => {
25 match env::var($e) {
26 Ok(el) => el,
27 Err(err) => {
28 let error_message =
29 format!("{} is not defined in the environment. Error: {}", $e, err);
30 return Err(Error::EnvironmentVariableMissing(error_message));
31 }
32 }
33 };
34}
35
36macro_rules! env_or {
37 ($e: expr, $d: expr) => {
38 match env::var($e) {
39 Ok(el) => el,
40 Err(_) => $d,
41 }
42 };
43}
44
45macro_rules! env_or_none {
46 ($e: expr) => {
47 match env::var($e) {
48 Ok(el) => Some(el),
49 Err(_) => None,
50 }
51 };
52}
53
54#[derive(Default, Clone, Debug)]
55pub struct ClientConfig {
57 pub max_retries: u32,
59 pub max_retry_delay_ms: Option<u64>,
61 pub timeout_ms: Option<u64>,
64 pub initial_delay_ms: Option<u64>,
66}
67
68#[derive(Clone)]
69pub struct CogniteClient {
71 pub api_client: Arc<ApiClient>,
74
75 pub assets: AssetsResource,
77 pub events: EventsResource,
79 pub files: Files,
81 pub time_series: TimeSeriesResource,
83 pub groups: GroupsResource,
85 pub raw: RawResource,
87 pub data_sets: DataSetsResource,
89 pub labels: LabelsResource,
91 pub relationships: RelationshipsResource,
93 pub ext_pipes: ExtPipesResource,
95 pub ext_pipe_runs: ExtPipeRunsResource,
97 pub sequences: SequencesResource,
99 pub sessions: SessionsResource,
101 pub models: Models,
103}
104
105static COGNITE_BASE_URL: &str = "COGNITE_BASE_URL";
106static COGNITE_PROJECT_NAME: &str = "COGNITE_PROJECT";
107static COGNITE_CLIENT_ID: &str = "COGNITE_CLIENT_ID";
108static COGNITE_CLIENT_SECRET: &str = "COGNITE_CLIENT_SECRET";
109static COGNITE_TOKEN_URL: &str = "COGNITE_TOKEN_URL";
110static COGNITE_RESOURCE: &str = "COGNITE_RESOURCE";
111static COGNITE_AUDIENCE: &str = "COGNITE_AUDIENCE";
112static COGNITE_SCOPES: &str = "COGNITE_SCOPES";
113
114impl CogniteClient {
115 pub fn new_oidc(app_name: &str, config: Option<ClientConfig>) -> Result<Self> {
133 let api_base_url = env_or!(COGNITE_BASE_URL, "https://api.cognitedata.com/".to_string());
134 let project_name = env_or_error!(COGNITE_PROJECT_NAME);
135 let auth_config = AuthenticatorConfig {
136 client_id: env_or_error!(COGNITE_CLIENT_ID),
137 token_url: env_or_error!(COGNITE_TOKEN_URL),
138 secret: env_or_error!(COGNITE_CLIENT_SECRET),
139 resource: env_or_none!(COGNITE_RESOURCE),
140 audience: env_or_none!(COGNITE_AUDIENCE),
141 scopes: env_or_none!(COGNITE_SCOPES),
142 default_expires_in: None,
143 };
144
145 CogniteClient::new_from_oidc(&api_base_url, auth_config, &project_name, app_name, config)
146 }
147
148 pub fn new_custom_auth(
158 api_base_url: &str,
159 project_name: &str,
160 auth: AuthHeaderManager,
161 app_name: &str,
162 config: Option<ClientConfig>,
163 ) -> Result<Self> {
164 let api_base_path = format!("{}/api/{}/projects/{}", api_base_url, "v1", project_name);
165 let client = Self::get_client(config.unwrap_or_default(), auth, None, None)?;
166 let api_client = ApiClient::new(&api_base_path, app_name, client.clone());
167
168 Self::new_internal(api_client)
169 }
170
171 fn get_client(
172 config: ClientConfig,
173 authenticator: AuthHeaderManager,
174 client: Option<Client>,
175 middleware: Option<Vec<Arc<dyn Middleware>>>,
176 ) -> Result<ClientWithMiddleware> {
177 let client = if let Some(client) = client {
178 client
179 } else {
180 #[allow(unused_mut)]
181 let mut builder = Client::builder();
182 #[cfg(not(target_arch = "wasm32"))]
184 if let Some(timeout) = config.timeout_ms {
185 builder = builder.timeout(std::time::Duration::from_millis(timeout));
186 }
187
188 builder.build()?
189 };
190
191 let mut builder = ClientBuilder::new(client);
192 if config.max_retries > 0 {
193 builder = builder.with(CustomRetryMiddleware::new(
194 config.max_retries,
195 config.max_retry_delay_ms.unwrap_or(5 * 60 * 1000),
196 config.initial_delay_ms.unwrap_or(125),
197 ));
198 }
199 builder = builder.with(AuthenticatorMiddleware::new(authenticator)?);
200 if let Some(mw) = middleware {
201 for ware in mw {
202 builder = builder.with_arc(ware);
203 }
204 }
205 Ok(builder.build())
206 }
207
208 fn new_from_builder(
209 auth: AuthHeaderManager,
210 config: ClientConfig,
211 client: Option<Client>,
212 app_name: String,
213 project: String,
214 base_url: String,
215 middleware: Option<Vec<Arc<dyn Middleware>>>,
216 ) -> Result<Self> {
217 let api_base_path = format!("{}/api/{}/projects/{}", base_url, "v1", project);
218 let client = Self::get_client(config, auth, client, middleware)?;
219 let api_client = ApiClient::new(&api_base_path, &app_name, client.clone());
220 Self::new_internal(api_client)
221 }
222
223 fn new_internal(api_client: ApiClient) -> Result<Self> {
224 let ac = Arc::new(api_client);
225 Ok(CogniteClient {
226 api_client: ac.clone(),
227
228 assets: AssetsResource::new(ac.clone()),
229 events: EventsResource::new(ac.clone()),
230 files: Files::new(ac.clone()),
231 groups: GroupsResource::new(ac.clone()),
232 time_series: TimeSeriesResource::new(ac.clone()),
233 raw: RawResource::new(ac.clone()),
234 data_sets: DataSetsResource::new(ac.clone()),
235 labels: LabelsResource::new(ac.clone()),
236 relationships: RelationshipsResource::new(ac.clone()),
237 ext_pipes: ExtPipesResource::new(ac.clone()),
238 ext_pipe_runs: ExtPipeRunsResource::new(ac.clone()),
239 sequences: SequencesResource::new(ac.clone()),
240 sessions: SessionsResource::new(ac.clone()),
241 models: Models::new(ac),
242 })
243 }
244
245 pub fn new_from_oidc(
255 api_base_url: &str,
256 auth_config: AuthenticatorConfig,
257 project_name: &str,
258 app_name: &str,
259 config: Option<ClientConfig>,
260 ) -> Result<Self> {
261 let authenticator = Authenticator::new(auth_config);
262 let api_base_path = format!("{}/api/{}/projects/{}", api_base_url, "v1", project_name);
263 let auth = AuthHeaderManager::OIDCToken(Arc::new(authenticator));
264 let client = Self::get_client(config.unwrap_or_default(), auth, None, None)?;
265 let api_client = ApiClient::new(&api_base_path, app_name, client.clone());
266
267 Self::new_internal(api_client)
268 }
269
270 pub fn builder() -> Builder {
272 Builder::default()
273 }
274}
275
276#[derive(Default)]
278pub struct Builder {
279 auth: Option<AuthHeaderManager>,
280 config: Option<ClientConfig>,
281 client: Option<Client>,
282 app_name: Option<String>,
283 project: Option<String>,
284 base_url: Option<String>,
285 custom_middleware: Option<Vec<Arc<dyn Middleware>>>,
286}
287
288impl Builder {
289 pub fn set_custom_auth(&mut self, auth: AuthHeaderManager) -> &mut Self {
295 self.auth = Some(auth);
296 self
297 }
298
299 pub fn set_oidc_credentials(&mut self, auth: AuthenticatorConfig) -> &mut Self {
305 self.auth = Some(AuthHeaderManager::OIDCToken(Arc::new(Authenticator::new(
306 auth,
307 ))));
308 self
309 }
310
311 pub fn set_project(&mut self, project: &str) -> &mut Self {
317 self.project = Some(project.to_owned());
318 self
319 }
320
321 pub fn set_app_name(&mut self, app_name: &str) -> &mut Self {
323 self.app_name = Some(app_name.to_owned());
324 self
325 }
326
327 pub fn set_internal_client(&mut self, client: Client) -> &mut Self {
336 self.client = Some(client);
337 self
338 }
339
340 pub fn set_client_config(&mut self, config: ClientConfig) -> &mut Self {
346 self.config = Some(config);
347 self
348 }
349
350 pub fn set_base_url(&mut self, base_url: &str) -> &mut Self {
356 self.base_url = Some(base_url.to_owned());
357 self
358 }
359
360 pub fn with_custom_middleware(&mut self, middleware: Arc<dyn Middleware>) -> &mut Self {
366 match &mut self.custom_middleware {
367 Some(x) => x.push(middleware),
368 None => self.custom_middleware = Some(vec![middleware]),
369 }
370 self
371 }
372
373 pub fn build(self) -> Result<CogniteClient> {
375 let auth = self
376 .auth
377 .ok_or_else(|| Error::Config("Some form of auth is required".to_string()))?;
378 let config = self.config.unwrap_or_default();
379 let client = self.client;
380 let app_name = self
381 .app_name
382 .ok_or_else(|| Error::Config("App name is required".to_string()))?;
383 let project = self
384 .project
385 .ok_or_else(|| Error::Config("Project is required".to_string()))?;
386 let base_url = self
387 .base_url
388 .unwrap_or_else(|| "https://api.cognitedata.com/".to_owned());
389
390 CogniteClient::new_from_builder(
391 auth,
392 config,
393 client,
394 app_name,
395 project,
396 base_url,
397 self.custom_middleware,
398 )
399 }
400}