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
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}