1use reqwest::Client;
2use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Middleware};
3use std::env;
4use std::sync::Arc;
5use std::time::Duration;
6
7use super::{ApiClient, Error, Result};
8use crate::api::core::sequences::SequencesResource;
9use crate::api::data_modeling::Models;
10use crate::api::iam::groups::GroupsResource;
11use crate::api::iam::sessions::SessionsResource;
12use crate::auth::AuthenticatorMiddleware;
13use crate::retry::CustomRetryMiddleware;
14use crate::AuthHeaderManager;
15use crate::{
16 assets::AssetsResource, datasets::DataSetsResource, events::EventsResource,
17 extpipes::ExtPipeRunsResource, extpipes::ExtPipesResource, files::Files,
18 labels::LabelsResource, raw::RawResource, relationships::RelationshipsResource,
19 time_series::TimeSeriesResource,
20};
21
22use crate::api::authenticator::{Authenticator, AuthenticatorConfig};
23
24macro_rules! env_or_error {
25 ($e: expr) => {
26 match env::var($e) {
27 Ok(el) => el,
28 Err(err) => {
29 let error_message =
30 format!("{} is not defined in the environment. Error: {}", $e, err);
31 return Err(Error::EnvironmentVariableMissing(error_message));
32 }
33 }
34 };
35}
36
37macro_rules! env_or {
38 ($e: expr, $d: expr) => {
39 match env::var($e) {
40 Ok(el) => el,
41 Err(_) => $d,
42 }
43 };
44}
45
46macro_rules! env_or_none {
47 ($e: expr) => {
48 match env::var($e) {
49 Ok(el) => Some(el),
50 Err(_) => None,
51 }
52 };
53}
54
55#[derive(Default, Clone, Debug)]
56pub struct ClientConfig {
58 pub max_retries: u32,
60 pub max_retry_delay_ms: Option<u64>,
62 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 let mut builder = Client::builder();
181 if let Some(timeout) = config.timeout_ms {
183 builder = builder.timeout(Duration::from_millis(timeout));
184 }
185
186 builder.build()?
187 };
188
189 let mut builder = ClientBuilder::new(client);
190 if config.max_retries > 0 {
191 builder = builder.with(CustomRetryMiddleware::new(
192 config.max_retries,
193 config.max_retry_delay_ms.unwrap_or(5 * 60 * 1000),
194 config.initial_delay_ms.unwrap_or(125),
195 ));
196 }
197 builder = builder.with(AuthenticatorMiddleware::new(authenticator)?);
198 if let Some(mw) = middleware {
199 for ware in mw {
200 builder = builder.with_arc(ware);
201 }
202 }
203 Ok(builder.build())
204 }
205
206 fn new_from_builder(
207 auth: AuthHeaderManager,
208 config: ClientConfig,
209 client: Option<Client>,
210 app_name: String,
211 project: String,
212 base_url: String,
213 middleware: Option<Vec<Arc<dyn Middleware>>>,
214 ) -> Result<Self> {
215 let api_base_path = format!("{}/api/{}/projects/{}", base_url, "v1", project);
216 let client = Self::get_client(config, auth, client, middleware)?;
217 let api_client = ApiClient::new(&api_base_path, &app_name, client.clone());
218 Self::new_internal(api_client)
219 }
220
221 fn new_internal(api_client: ApiClient) -> Result<Self> {
222 let ac = Arc::new(api_client);
223 Ok(CogniteClient {
224 api_client: ac.clone(),
225
226 assets: AssetsResource::new(ac.clone()),
227 events: EventsResource::new(ac.clone()),
228 files: Files::new(ac.clone()),
229 groups: GroupsResource::new(ac.clone()),
230 time_series: TimeSeriesResource::new(ac.clone()),
231 raw: RawResource::new(ac.clone()),
232 data_sets: DataSetsResource::new(ac.clone()),
233 labels: LabelsResource::new(ac.clone()),
234 relationships: RelationshipsResource::new(ac.clone()),
235 ext_pipes: ExtPipesResource::new(ac.clone()),
236 ext_pipe_runs: ExtPipeRunsResource::new(ac.clone()),
237 sequences: SequencesResource::new(ac.clone()),
238 sessions: SessionsResource::new(ac.clone()),
239 models: Models::new(ac),
240 })
241 }
242
243 pub fn new_from_oidc(
253 api_base_url: &str,
254 auth_config: AuthenticatorConfig,
255 project_name: &str,
256 app_name: &str,
257 config: Option<ClientConfig>,
258 ) -> Result<Self> {
259 let authenticator = Authenticator::new(auth_config);
260 let api_base_path = format!("{}/api/{}/projects/{}", api_base_url, "v1", project_name);
261 let auth = AuthHeaderManager::OIDCToken(Arc::new(authenticator));
262 let client = Self::get_client(config.unwrap_or_default(), auth, None, None)?;
263 let api_client = ApiClient::new(&api_base_path, app_name, client.clone());
264
265 Self::new_internal(api_client)
266 }
267
268 pub fn builder() -> Builder {
270 Builder::default()
271 }
272}
273
274#[derive(Default)]
276pub struct Builder {
277 auth: Option<AuthHeaderManager>,
278 config: Option<ClientConfig>,
279 client: Option<Client>,
280 app_name: Option<String>,
281 project: Option<String>,
282 base_url: Option<String>,
283 custom_middleware: Option<Vec<Arc<dyn Middleware>>>,
284}
285
286impl Builder {
287 pub fn set_custom_auth(&mut self, auth: AuthHeaderManager) -> &mut Self {
293 self.auth = Some(auth);
294 self
295 }
296
297 pub fn set_oidc_credentials(&mut self, auth: AuthenticatorConfig) -> &mut Self {
303 self.auth = Some(AuthHeaderManager::OIDCToken(Arc::new(Authenticator::new(
304 auth,
305 ))));
306 self
307 }
308
309 pub fn set_project(&mut self, project: &str) -> &mut Self {
315 self.project = Some(project.to_owned());
316 self
317 }
318
319 pub fn set_app_name(&mut self, app_name: &str) -> &mut Self {
321 self.app_name = Some(app_name.to_owned());
322 self
323 }
324
325 pub fn set_internal_client(&mut self, client: Client) -> &mut Self {
334 self.client = Some(client);
335 self
336 }
337
338 pub fn set_client_config(&mut self, config: ClientConfig) -> &mut Self {
344 self.config = Some(config);
345 self
346 }
347
348 pub fn set_base_url(&mut self, base_url: &str) -> &mut Self {
354 self.base_url = Some(base_url.to_owned());
355 self
356 }
357
358 pub fn with_custom_middleware(&mut self, middleware: Arc<dyn Middleware>) -> &mut Self {
364 match &mut self.custom_middleware {
365 Some(x) => x.push(middleware),
366 None => self.custom_middleware = Some(vec![middleware]),
367 }
368 self
369 }
370
371 pub fn build(self) -> Result<CogniteClient> {
373 let auth = self
374 .auth
375 .ok_or_else(|| Error::Config("Some form of auth is required".to_string()))?;
376 let config = self.config.unwrap_or_default();
377 let client = self.client;
378 let app_name = self
379 .app_name
380 .ok_or_else(|| Error::Config("App name is required".to_string()))?;
381 let project = self
382 .project
383 .ok_or_else(|| Error::Config("Project is required".to_string()))?;
384 let base_url = self
385 .base_url
386 .unwrap_or_else(|| "https://api.cognitedata.com/".to_owned());
387
388 CogniteClient::new_from_builder(
389 auth,
390 config,
391 client,
392 app_name,
393 project,
394 base_url,
395 self.custom_middleware,
396 )
397 }
398}