Skip to main content

context69_sdk/
client.rs

1mod groups;
2mod library;
3mod search;
4mod settings;
5mod sources;
6mod transport;
7mod user_directory;
8
9use std::{sync::Arc, time::Duration};
10
11use context69_contracts::{AuthMeResponse, HealthResponse};
12use reqwest::{Method, Url, header::USER_AGENT};
13use tokio::sync::RwLock;
14
15use crate::Error;
16
17pub use groups::{
18    GroupApi, GroupChildrenApi, GroupDocumentsApi, GroupMemberApi, GroupMembersApi,
19    GroupMetadataIndexesApi, GroupSourceFolderApi, GroupSourceFoldersApi, GroupsApi,
20};
21pub use library::{
22    GroupLibraryApi, GroupLibraryTextsApi, LibraryApi, LibraryFileApi, LibraryFilesApi,
23    LibraryFolderApi, LibraryFoldersApi, LibraryJobApi, LibraryTextsApi,
24};
25pub use search::{DocumentApi, SearchApi};
26pub use settings::{DoclingSettingsApi, RuntimeSettingsApi, SearchSettingsApi, SettingsApi};
27pub use sources::{SourceApi, SourceConnectionApi, SourceConnectionsApi, SourcesApi};
28pub use user_directory::UserDirectoryApi;
29
30pub(crate) const PERSONAL_ACCESS_TOKEN_PREFIX: &str = "ctx_pat_";
31
32#[derive(Debug, Clone, Default)]
33struct SessionState {
34    personal_access_token: Option<String>,
35}
36
37#[derive(Clone)]
38pub struct Context69Client {
39    client: reqwest::Client,
40    base_url: Url,
41    session: Arc<RwLock<SessionState>>,
42}
43
44#[derive(Debug)]
45pub struct Context69ClientBuilder {
46    base_url: Option<Url>,
47    user_agent: Option<String>,
48    timeout: Option<Duration>,
49    personal_access_token: Option<String>,
50}
51
52impl Context69Client {
53    pub fn builder() -> Context69ClientBuilder {
54        Context69ClientBuilder {
55            base_url: None,
56            user_agent: None,
57            timeout: None,
58            personal_access_token: None,
59        }
60    }
61
62    pub fn with_personal_access_token(
63        &self,
64        token: impl Into<String>,
65    ) -> Result<Context69Client, Error> {
66        Ok(Context69Client {
67            client: self.client.clone(),
68            base_url: self.base_url.clone(),
69            session: Arc::new(RwLock::new(SessionState {
70                personal_access_token: Some(transport::validate_personal_access_token(
71                    token.into(),
72                )?),
73            })),
74        })
75    }
76
77    pub fn user_directory(&self) -> UserDirectoryApi<'_> {
78        UserDirectoryApi::new(self)
79    }
80
81    pub fn groups(&self) -> GroupsApi<'_> {
82        GroupsApi::new(self)
83    }
84
85    pub fn group(&self, group_path: impl Into<String>) -> GroupApi<'_> {
86        GroupApi::new(self, group_path.into())
87    }
88
89    pub fn sources(&self) -> SourcesApi<'_> {
90        SourcesApi::new(self)
91    }
92
93    pub fn source(&self, source_key: impl Into<String>) -> SourceApi<'_> {
94        SourceApi::new(self, source_key.into())
95    }
96
97    pub fn source_connections(&self) -> SourceConnectionsApi<'_> {
98        SourceConnectionsApi::new(self)
99    }
100
101    pub fn source_connection(&self, name: impl Into<String>) -> SourceConnectionApi<'_> {
102        SourceConnectionApi::new(self, name.into())
103    }
104
105    pub fn library(&self) -> LibraryApi<'_> {
106        LibraryApi::new(self)
107    }
108
109    pub fn settings(&self) -> SettingsApi<'_> {
110        SettingsApi::new(self)
111    }
112
113    pub fn search(&self) -> SearchApi<'_> {
114        SearchApi::new(self)
115    }
116
117    pub fn document(&self, document_id: i64) -> DocumentApi<'_> {
118        DocumentApi::new(self, document_id)
119    }
120
121    pub async fn me(&self) -> Result<AuthMeResponse, Error> {
122        self.execute_json(self.authorized_request(Method::GET, "/v1/auth/me").await?)
123            .await
124    }
125
126    pub async fn healthz(&self) -> Result<HealthResponse, Error> {
127        let response = self.client.get(self.url("/healthz")?).send().await?;
128        self.read_json_response(response).await
129    }
130}
131
132impl Context69ClientBuilder {
133    pub fn base_url(mut self, base_url: &str) -> Result<Self, Error> {
134        let mut url =
135            Url::parse(base_url).map_err(|_| Error::InvalidBaseUrl(base_url.to_string()))?;
136        if !url.path().ends_with('/') {
137            url.set_path(&format!("{}/", url.path()));
138        }
139        self.base_url = Some(url);
140        Ok(self)
141    }
142
143    pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
144        self.user_agent = Some(user_agent.into());
145        self
146    }
147
148    pub fn timeout(mut self, timeout: Duration) -> Result<Self, Error> {
149        if timeout.is_zero() {
150            return Err(Error::InvalidTimeout(timeout));
151        }
152        self.timeout = Some(timeout);
153        Ok(self)
154    }
155
156    pub fn with_personal_access_token(mut self, token: impl Into<String>) -> Result<Self, Error> {
157        self.personal_access_token = Some(transport::validate_personal_access_token(token.into())?);
158        Ok(self)
159    }
160
161    pub fn build(self) -> Result<Context69Client, Error> {
162        let base_url = self
163            .base_url
164            .ok_or_else(|| Error::InvalidBaseUrl("missing base_url".to_string()))?;
165        let mut builder = reqwest::Client::builder();
166        if let Some(user_agent) = self.user_agent {
167            builder = builder.default_headers({
168                let mut headers = reqwest::header::HeaderMap::new();
169                headers.insert(
170                    USER_AGENT,
171                    user_agent
172                        .parse()
173                        .map_err(|_| Error::InvalidHeader(user_agent.clone()))?,
174                );
175                headers
176            });
177        }
178        if let Some(timeout) = self.timeout {
179            builder = builder.timeout(timeout);
180        }
181        Ok(Context69Client {
182            client: builder.build()?,
183            base_url,
184            session: Arc::new(RwLock::new(SessionState {
185                personal_access_token: self.personal_access_token,
186            })),
187        })
188    }
189}
190
191#[cfg(test)]
192mod tests;