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