Skip to main content

cloudiful_docling_convert/processor/service/
mod.rs

1use crate::api::DoclingClient;
2use crate::document::{
3    ConvertOptions, ConvertRequest, ConvertedDocument, ConvertedFile, FileConvertRequest, InputKind,
4};
5use crate::error::{PdfConvertError, Result};
6use crate::models::TaskStatusResponse;
7use std::future::Future;
8
9mod output;
10mod remote;
11mod text;
12
13#[cfg(test)]
14mod tests;
15
16pub struct DocumentConverter {
17    pub(crate) docling_client: DoclingClient,
18}
19
20impl DocumentConverter {
21    pub fn new(docling_client: DoclingClient) -> Self {
22        Self { docling_client }
23    }
24
25    pub async fn convert(&self, request: ConvertRequest) -> Result<ConvertedDocument> {
26        self.convert_with_progress(request, |_, _| async {}).await
27    }
28
29    pub async fn convert_async(&self, request: ConvertRequest) -> Result<ConvertedDocument> {
30        self.convert_async_with_progress(request, |_, _| async {})
31            .await
32    }
33
34    pub async fn convert_with_progress<F, Fut>(
35        &self,
36        request: ConvertRequest,
37        mut on_progress: F,
38    ) -> Result<ConvertedDocument>
39    where
40        F: FnMut(usize, usize) -> Fut + Send,
41        Fut: Future<Output = ()> + Send,
42    {
43        self.convert_internal(request, false, &mut on_progress)
44            .await
45    }
46
47    pub async fn convert_async_with_progress<F, Fut>(
48        &self,
49        request: ConvertRequest,
50        mut on_progress: F,
51    ) -> Result<ConvertedDocument>
52    where
53        F: FnMut(usize, usize) -> Fut + Send,
54        Fut: Future<Output = ()> + Send,
55    {
56        self.convert_internal(request, true, &mut on_progress).await
57    }
58
59    pub async fn convert_async_with_docling_progress<F, Fut>(
60        &self,
61        request: ConvertRequest,
62        mut on_status: F,
63    ) -> Result<ConvertedDocument>
64    where
65        F: FnMut(TaskStatusResponse) -> Fut + Send,
66        Fut: Future<Output = ()> + Send,
67    {
68        let input_kind = request.validate()?;
69        match (&request.options, input_kind) {
70            (ConvertOptions::Text(options), InputKind::Text) => {
71                self.convert_text(&request.input, options, &request.output_formats)
72            }
73            (ConvertOptions::Generic(options), _) if input_kind.uses_generic_convert_options() => {
74                self.convert_remote_with_docling_progress(
75                    &request.input,
76                    options,
77                    &request.output_formats,
78                    &mut on_status,
79                )
80                .await
81            }
82            (ConvertOptions::Pdf(options), InputKind::Pdf) => {
83                self.convert_remote_with_docling_progress(
84                    &request.input,
85                    options,
86                    &request.output_formats,
87                    &mut on_status,
88                )
89                .await
90            }
91            _ => Err(PdfConvertError::validation_error(
92                "request",
93                "input kind and convert options do not match",
94            )),
95        }
96    }
97
98    async fn convert_internal<F, Fut>(
99        &self,
100        request: ConvertRequest,
101        asynchronous: bool,
102        on_progress: &mut F,
103    ) -> Result<ConvertedDocument>
104    where
105        F: FnMut(usize, usize) -> Fut + Send,
106        Fut: Future<Output = ()> + Send,
107    {
108        let input_kind = request.validate()?;
109        match (&request.options, input_kind) {
110            (ConvertOptions::Text(options), InputKind::Text) => {
111                let document =
112                    self.convert_text(&request.input, options, &request.output_formats)?;
113                on_progress(1, 1).await;
114                Ok(document)
115            }
116            (ConvertOptions::Generic(options), _) if input_kind.uses_generic_convert_options() => {
117                self.convert_remote(
118                    &request.input,
119                    options,
120                    &request.output_formats,
121                    asynchronous,
122                    on_progress,
123                )
124                .await
125            }
126            (ConvertOptions::Pdf(options), InputKind::Pdf) => {
127                self.convert_remote(
128                    &request.input,
129                    options,
130                    &request.output_formats,
131                    asynchronous,
132                    on_progress,
133                )
134                .await
135            }
136            _ => Err(PdfConvertError::validation_error(
137                "request",
138                "input kind and convert options do not match",
139            )),
140        }
141    }
142
143    pub async fn convert_to_file(&self, request: FileConvertRequest) -> Result<ConvertedFile> {
144        self.convert_to_file_with_progress(request, |_, _| async {})
145            .await
146    }
147
148    pub async fn convert_to_file_with_progress<F, Fut>(
149        &self,
150        request: FileConvertRequest,
151        on_progress: F,
152    ) -> Result<ConvertedFile>
153    where
154        F: FnMut(usize, usize) -> Fut + Send,
155        Fut: Future<Output = ()> + Send,
156    {
157        self.convert_to_file_internal(request, false, on_progress)
158            .await
159    }
160
161    pub async fn convert_to_file_async(
162        &self,
163        request: FileConvertRequest,
164    ) -> Result<ConvertedFile> {
165        self.convert_to_file_async_with_progress(request, |_, _| async {})
166            .await
167    }
168
169    pub async fn convert_to_file_async_with_progress<F, Fut>(
170        &self,
171        request: FileConvertRequest,
172        on_progress: F,
173    ) -> Result<ConvertedFile>
174    where
175        F: FnMut(usize, usize) -> Fut + Send,
176        Fut: Future<Output = ()> + Send,
177    {
178        self.convert_to_file_internal(request, true, on_progress)
179            .await
180    }
181
182    pub async fn convert_to_file_async_with_docling_progress<F, Fut>(
183        &self,
184        request: FileConvertRequest,
185        on_status: F,
186    ) -> Result<ConvertedFile>
187    where
188        F: FnMut(TaskStatusResponse) -> Fut + Send,
189        Fut: Future<Output = ()> + Send,
190    {
191        let selected_output = request.selected_output;
192        let output_dir = request.output_dir.clone();
193        let overwrite = request.overwrite;
194        let document = self
195            .convert_async_with_docling_progress(request.request, on_status)
196            .await?;
197        let output_path =
198            Self::calculate_output_path(&output_dir, &document.filename, selected_output);
199
200        if !overwrite && output_path.exists() {
201            return Err(PdfConvertError::operation_error(
202                "writing output",
203                format!(
204                    "output already exists and overwrite is disabled: {}",
205                    output_path.display()
206                ),
207            ));
208        }
209
210        Self::write_output_file(&output_path, &document, selected_output).await?;
211        Ok(ConvertedFile {
212            document,
213            output_paths: vec![output_path],
214        })
215    }
216
217    async fn convert_to_file_internal<F, Fut>(
218        &self,
219        request: FileConvertRequest,
220        asynchronous: bool,
221        on_progress: F,
222    ) -> Result<ConvertedFile>
223    where
224        F: FnMut(usize, usize) -> Fut + Send,
225        Fut: Future<Output = ()> + Send,
226    {
227        let selected_output = request.selected_output;
228        let output_dir = request.output_dir.clone();
229        let overwrite = request.overwrite;
230        let document = if asynchronous {
231            self.convert_async_with_progress(request.request, on_progress)
232                .await?
233        } else {
234            self.convert_with_progress(request.request, on_progress)
235                .await?
236        };
237        let output_path =
238            Self::calculate_output_path(&output_dir, &document.filename, selected_output);
239
240        if !overwrite && output_path.exists() {
241            return Err(PdfConvertError::operation_error(
242                "writing output",
243                format!(
244                    "output already exists and overwrite is disabled: {}",
245                    output_path.display()
246                ),
247            ));
248        }
249
250        Self::write_output_file(&output_path, &document, selected_output).await?;
251        Ok(ConvertedFile {
252            document,
253            output_paths: vec![output_path],
254        })
255    }
256}