Skip to main content

context69_sdk/
client.rs

1mod facade;
2mod transport;
3
4use std::{sync::Arc, time::Duration};
5
6use reqwest::{Url, header::USER_AGENT};
7use tokio::sync::RwLock;
8
9use crate::Error;
10
11pub use facade::{
12    AuthMeResponse, BatchGetDocumentsRequest, BatchGetDocumentsResponse, CompactSearchHit,
13    CompactSearchResponse, CreateMetadataIndexRequest, DeleteBatchRequest, DocumentChunkResponse,
14    DocumentKey, DocumentResponse, EnsureScopeResponse, FileBatchItem, FileBatchRequest,
15    FileMetadata, GenericTaskRequest, GroupKind, GroupResponse, HealthResponse, MetadataDataType,
16    MetadataFilter, MetadataFilterOperator, MetadataValueKind, RerunTaskResponse,
17    ScopeMetadataIndex, ScopeSpec, SearchRequest, TaskItemResponse, TaskItemStatus,
18    TaskItemsResponse, TaskKind, TaskListQuery, TaskPageResponse, TaskProgress, TaskRef,
19    TaskResponse, TaskRetryResponse, TaskStatus, TextBatchItem, TextBatchRequest,
20    TextContentFormat, TranslationDirective, TranslationStatus, UrlBatchItem, UrlBatchRequest,
21    Visibility, WaitOptions,
22};
23
24pub(crate) const PERSONAL_ACCESS_TOKEN_PREFIX: &str = "ctx_pat_";
25
26#[derive(Debug, Clone, Default)]
27struct SessionState {
28    personal_access_token: Option<String>,
29}
30
31#[derive(Clone)]
32pub struct Context69Client {
33    client: reqwest::Client,
34    base_url: Url,
35    session: Arc<RwLock<SessionState>>,
36}
37
38#[derive(Debug)]
39pub struct Context69ClientBuilder {
40    base_url: Option<Url>,
41    user_agent: Option<String>,
42    timeout: Option<Duration>,
43    personal_access_token: Option<String>,
44}
45
46impl Context69Client {
47    pub fn builder() -> Context69ClientBuilder {
48        Context69ClientBuilder {
49            base_url: None,
50            user_agent: None,
51            timeout: None,
52            personal_access_token: None,
53        }
54    }
55
56    pub fn with_personal_access_token(
57        &self,
58        token: impl Into<String>,
59    ) -> Result<Context69Client, Error> {
60        Ok(Context69Client {
61            client: self.client.clone(),
62            base_url: self.base_url.clone(),
63            session: Arc::new(RwLock::new(SessionState {
64                personal_access_token: Some(transport::validate_personal_access_token(
65                    token.into(),
66                )?),
67            })),
68        })
69    }
70}
71
72impl Context69ClientBuilder {
73    pub fn base_url(mut self, base_url: &str) -> Result<Self, Error> {
74        let mut url =
75            Url::parse(base_url).map_err(|_| Error::InvalidBaseUrl(base_url.to_string()))?;
76        if !url.path().ends_with('/') {
77            url.set_path(&format!("{}/", url.path()));
78        }
79        self.base_url = Some(url);
80        Ok(self)
81    }
82
83    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
84        self.user_agent = Some(user_agent.into());
85        self
86    }
87
88    pub fn timeout(mut self, timeout: Duration) -> Result<Self, Error> {
89        if timeout.is_zero() {
90            return Err(Error::InvalidTimeout(timeout));
91        }
92        self.timeout = Some(timeout);
93        Ok(self)
94    }
95
96    pub fn with_personal_access_token(mut self, token: impl Into<String>) -> Result<Self, Error> {
97        self.personal_access_token = Some(transport::validate_personal_access_token(token.into())?);
98        Ok(self)
99    }
100
101    pub fn build(self) -> Result<Context69Client, Error> {
102        let base_url = self
103            .base_url
104            .ok_or_else(|| Error::InvalidBaseUrl("missing base_url".to_string()))?;
105        let mut builder = reqwest::Client::builder();
106        if let Some(user_agent) = self.user_agent {
107            builder = builder.default_headers({
108                let mut headers = reqwest::header::HeaderMap::new();
109                headers.insert(
110                    USER_AGENT,
111                    user_agent
112                        .parse()
113                        .map_err(|_| Error::InvalidHeader(user_agent.clone()))?,
114                );
115                headers
116            });
117        }
118        if let Some(timeout) = self.timeout {
119            builder = builder.timeout(timeout);
120        }
121        Ok(Context69Client {
122            client: builder.build()?,
123            base_url,
124            session: Arc::new(RwLock::new(SessionState {
125                personal_access_token: self.personal_access_token,
126            })),
127        })
128    }
129}