paperless_api_client/
lib.rs1#![allow(mismatched_lifetime_syntaxes)]
57#![allow(missing_docs)]
58#![allow(unused_imports)]
59#![allow(clippy::needless_lifetimes)]
60#![allow(clippy::too_many_arguments)]
61#![cfg_attr(docsrs, feature(doc_cfg))]
62
63#[cfg(feature = "requests")]
64pub mod bulk_edit_objects;
65#[cfg(feature = "requests")]
66pub mod config;
67#[cfg(feature = "requests")]
68pub mod correspondents;
69#[cfg(feature = "requests")]
70pub mod custom_fields;
71#[cfg(feature = "requests")]
72pub mod document_types;
73#[cfg(feature = "requests")]
74pub mod documents;
75#[cfg(feature = "requests")]
76pub mod groups;
77#[cfg(feature = "requests")]
78pub mod logs;
79#[cfg(feature = "requests")]
80pub mod mail_accounts;
81#[cfg(feature = "requests")]
82pub mod mail_rules;
83mod methods;
84#[cfg(feature = "requests")]
85pub mod oauth;
86#[cfg(feature = "requests")]
87pub mod processed_mail;
88#[cfg(feature = "requests")]
89pub mod profile;
90#[cfg(feature = "requests")]
91pub mod remote_version;
92#[cfg(feature = "requests")]
93pub mod saved_views;
94#[cfg(feature = "requests")]
95pub mod search;
96#[cfg(feature = "requests")]
97pub mod share_links;
98#[cfg(feature = "requests")]
99pub mod statistics;
100#[cfg(feature = "requests")]
101pub mod status;
102#[cfg(feature = "requests")]
103pub mod storage_paths;
104#[cfg(feature = "requests")]
105pub mod tags;
106#[cfg(feature = "requests")]
107pub mod tasks;
108#[cfg(test)]
109mod tests;
110#[cfg(feature = "requests")]
111pub mod token;
112#[cfg(feature = "requests")]
113pub mod trash;
114pub mod types;
115#[cfg(feature = "requests")]
116pub mod ui_settings;
117#[cfg(feature = "requests")]
118pub mod users;
119#[cfg(feature = "requests")]
120pub mod workflow_actions;
121#[cfg(feature = "requests")]
122pub mod workflow_triggers;
123#[cfg(feature = "requests")]
124pub mod workflows;
125
126#[cfg(feature = "requests")]
127use std::env;
128
129#[cfg(not(target_arch = "wasm32"))]
130#[cfg(feature = "requests")]
131static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
132
133#[derive(Clone, Debug)]
135#[cfg(feature = "requests")]
136pub struct Client {
137 token: String,
138 base_url: String,
139
140 #[cfg(feature = "retry")]
141 client: reqwest_middleware::ClientWithMiddleware,
142 #[cfg(feature = "retry")]
143 #[cfg(not(target_arch = "wasm32"))]
144 #[allow(dead_code)]
145 client_http1_only: reqwest_middleware::ClientWithMiddleware,
146
147 #[cfg(not(feature = "retry"))]
148 client: reqwest::Client,
149 #[cfg(not(feature = "retry"))]
150 #[cfg(not(target_arch = "wasm32"))]
151 #[allow(dead_code)]
152 client_http1_only: reqwest::Client,
153}
154
155#[cfg(feature = "retry")]
157#[cfg(feature = "requests")]
158pub struct RequestBuilder(pub reqwest_middleware::RequestBuilder);
159#[cfg(not(feature = "retry"))]
160#[cfg(feature = "requests")]
161pub struct RequestBuilder(pub reqwest::RequestBuilder);
162
163#[cfg(feature = "requests")]
164impl Client {
165 #[tracing::instrument(skip(token))]
170 #[cfg(not(target_arch = "wasm32"))]
171 pub fn new_from_reqwest<T>(
172 token: T,
173 builder_http: reqwest::ClientBuilder,
174 builder_websocket: reqwest::ClientBuilder,
175 ) -> Self
176 where
177 T: ToString + std::fmt::Debug,
178 {
179 #[cfg(feature = "retry")]
180 {
181 let retry_policy =
183 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
184 match (builder_http.build(), builder_websocket.build()) {
185 (Ok(c), Ok(c1)) => {
186 let client = reqwest_middleware::ClientBuilder::new(c)
187 .with(reqwest_tracing::TracingMiddleware::default())
189 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
191 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
192 |req: &reqwest::Request| req.try_clone().is_some(),
193 ))
194 .build();
195 let client_http1_only = reqwest_middleware::ClientBuilder::new(c1)
196 .with(reqwest_tracing::TracingMiddleware::default())
197 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
198 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
199 |req: &reqwest::Request| req.try_clone().is_some(),
200 ))
201 .build();
202 Client {
203 token: token.to_string(),
204 base_url: "https://your-paperles.url/api".to_string(),
205
206 client,
207 client_http1_only,
208 }
209 }
210 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {e:?}"),
211 }
212 }
213 #[cfg(not(feature = "retry"))]
214 {
215 match (builder_http.build(), builder_websocket.build()) {
216 (Ok(c), Ok(c1)) => Client {
217 token: token.to_string(),
218 base_url: "https://your-paperles.url/api".to_string(),
219
220 client: c,
221 client_http1_only: c1,
222 },
223 (Err(e), _) | (_, Err(e)) => panic!("creating reqwest client failed: {:?}", e),
224 }
225 }
226 }
227
228 #[tracing::instrument(skip(token))]
233 #[cfg(target_arch = "wasm32")]
234 pub fn new_from_reqwest<T>(token: T, builder_http: reqwest::ClientBuilder) -> Self
235 where
236 T: ToString + std::fmt::Debug,
237 {
238 #[cfg(feature = "retry")]
239 {
240 let retry_policy =
242 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
243 match builder_http.build() {
244 Ok(c) => {
245 let client = reqwest_middleware::ClientBuilder::new(c)
246 .with(reqwest_tracing::TracingMiddleware::default())
248 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
250 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
251 |req: &reqwest::Request| req.try_clone().is_some(),
252 ))
253 .build();
254 Client {
255 token: token.to_string(),
256 base_url: "https://your-paperles.url/api".to_string(),
257
258 client,
259 }
260 }
261 Err(e) => panic!("creating reqwest client failed: {:?}", e),
262 }
263 }
264 #[cfg(not(feature = "retry"))]
265 {
266 match builder_http.build() {
267 Ok(c) => Client {
268 token: token.to_string(),
269 base_url: "https://your-paperles.url/api".to_string(),
270
271 client: c,
272 },
273 Err(e) => panic!("creating reqwest client failed: {:?}", e),
274 }
275 }
276 }
277
278 #[tracing::instrument(skip(token))]
282 pub fn new<T>(token: T) -> Self
283 where
284 T: ToString + std::fmt::Debug,
285 {
286 #[cfg(not(target_arch = "wasm32"))]
287 let client = reqwest::Client::builder()
288 .user_agent(APP_USER_AGENT)
289 .timeout(std::time::Duration::from_secs(600))
291 .connect_timeout(std::time::Duration::from_secs(60));
292 #[cfg(target_arch = "wasm32")]
293 let client = reqwest::Client::builder();
294 #[cfg(not(target_arch = "wasm32"))]
295 let client_http1 = reqwest::Client::builder()
296 .user_agent(APP_USER_AGENT)
298 .timeout(std::time::Duration::from_secs(600))
299 .connect_timeout(std::time::Duration::from_secs(60))
300 .http1_only();
301 #[cfg(not(target_arch = "wasm32"))]
302 return Self::new_from_reqwest(token, client, client_http1);
303 #[cfg(target_arch = "wasm32")]
304 Self::new_from_reqwest(token, client)
305 }
306
307 #[tracing::instrument]
309 pub fn set_base_url<H>(&mut self, base_url: H)
310 where
311 H: Into<String> + std::fmt::Display + std::fmt::Debug,
312 {
313 self.base_url = base_url.to_string().trim_end_matches('/').to_string();
314 }
315
316 #[tracing::instrument]
318 pub fn new_from_env() -> Self {
319 let token = env::var("PAPERLESS_API_CLIENT_API_TOKEN")
320 .expect("must set PAPERLESS_API_CLIENT_API_TOKEN");
321 let base_url = env::var("PAPERLESS_API_CLIENT_HOST")
322 .unwrap_or("https://your-paperles.url/api".to_string());
323
324 let mut c = Client::new(token);
325 c.set_base_url(base_url);
326 c
327 }
328
329 #[tracing::instrument]
331 pub async fn request_raw(
332 &self,
333 method: reqwest::Method,
334 uri: &str,
335 body: Option<reqwest::Body>,
336 ) -> anyhow::Result<RequestBuilder> {
337 let u = if uri.starts_with("https://") || uri.starts_with("http://") {
338 uri.to_string()
339 } else {
340 format!("{}/{}", self.base_url, uri.trim_start_matches('/'))
341 };
342
343 let mut req = self.client.request(method, &u);
344
345 req = req.bearer_auth(&self.token);
347
348 req = req.header(
350 reqwest::header::ACCEPT,
351 reqwest::header::HeaderValue::from_static("application/json"),
352 );
353 req = req.header(
354 reqwest::header::CONTENT_TYPE,
355 reqwest::header::HeaderValue::from_static("application/json"),
356 );
357
358 if let Some(body) = body {
359 req = req.body(body);
360 }
361
362 Ok(RequestBuilder(req))
363 }
364
365 pub fn bulk_edit_objects(&self) -> bulk_edit_objects::BulkEditObjects {
367 bulk_edit_objects::BulkEditObjects::new(self.clone())
368 }
369
370 pub fn config(&self) -> config::Config {
372 config::Config::new(self.clone())
373 }
374
375 pub fn correspondents(&self) -> correspondents::Correspondents {
377 correspondents::Correspondents::new(self.clone())
378 }
379
380 pub fn custom_fields(&self) -> custom_fields::CustomFields {
382 custom_fields::CustomFields::new(self.clone())
383 }
384
385 pub fn document_types(&self) -> document_types::DocumentTypes {
387 document_types::DocumentTypes::new(self.clone())
388 }
389
390 pub fn documents(&self) -> documents::Documents {
392 documents::Documents::new(self.clone())
393 }
394
395 pub fn groups(&self) -> groups::Groups {
397 groups::Groups::new(self.clone())
398 }
399
400 pub fn logs(&self) -> logs::Logs {
402 logs::Logs::new(self.clone())
403 }
404
405 pub fn mail_accounts(&self) -> mail_accounts::MailAccounts {
407 mail_accounts::MailAccounts::new(self.clone())
408 }
409
410 pub fn mail_rules(&self) -> mail_rules::MailRules {
412 mail_rules::MailRules::new(self.clone())
413 }
414
415 pub fn oauth(&self) -> oauth::Oauth {
417 oauth::Oauth::new(self.clone())
418 }
419
420 pub fn processed_mail(&self) -> processed_mail::ProcessedMail {
422 processed_mail::ProcessedMail::new(self.clone())
423 }
424
425 pub fn profile(&self) -> profile::Profile {
427 profile::Profile::new(self.clone())
428 }
429
430 pub fn remote_version(&self) -> remote_version::RemoteVersion {
432 remote_version::RemoteVersion::new(self.clone())
433 }
434
435 pub fn saved_views(&self) -> saved_views::SavedViews {
437 saved_views::SavedViews::new(self.clone())
438 }
439
440 pub fn search(&self) -> search::Search {
442 search::Search::new(self.clone())
443 }
444
445 pub fn share_links(&self) -> share_links::ShareLinks {
447 share_links::ShareLinks::new(self.clone())
448 }
449
450 pub fn statistics(&self) -> statistics::Statistics {
452 statistics::Statistics::new(self.clone())
453 }
454
455 pub fn status(&self) -> status::Status {
457 status::Status::new(self.clone())
458 }
459
460 pub fn storage_paths(&self) -> storage_paths::StoragePaths {
462 storage_paths::StoragePaths::new(self.clone())
463 }
464
465 pub fn tags(&self) -> tags::Tags {
467 tags::Tags::new(self.clone())
468 }
469
470 pub fn tasks(&self) -> tasks::Tasks {
472 tasks::Tasks::new(self.clone())
473 }
474
475 pub fn token(&self) -> token::Token {
477 token::Token::new(self.clone())
478 }
479
480 pub fn trash(&self) -> trash::Trash {
482 trash::Trash::new(self.clone())
483 }
484
485 pub fn ui_settings(&self) -> ui_settings::UiSettings {
487 ui_settings::UiSettings::new(self.clone())
488 }
489
490 pub fn users(&self) -> users::Users {
492 users::Users::new(self.clone())
493 }
494
495 pub fn workflow_actions(&self) -> workflow_actions::WorkflowActions {
497 workflow_actions::WorkflowActions::new(self.clone())
498 }
499
500 pub fn workflow_triggers(&self) -> workflow_triggers::WorkflowTriggers {
502 workflow_triggers::WorkflowTriggers::new(self.clone())
503 }
504
505 pub fn workflows(&self) -> workflows::Workflows {
507 workflows::Workflows::new(self.clone())
508 }
509}