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