1use super::{OcrResult, FormatsData, LineData};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ApiResponse {
11 pub request_id: String,
13
14 pub version: String,
16
17 pub image_width: u32,
19 pub image_height: u32,
20
21 pub is_printed: bool,
23 pub is_handwritten: bool,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub auto_rotate_confidence: Option<f32>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub auto_rotate_degrees: Option<i32>,
30
31 pub confidence: f32,
33 pub confidence_rate: f32,
34
35 #[serde(flatten)]
37 pub formats: FormatsData,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub line_data: Option<Vec<LineData>>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub error: Option<String>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub error_info: Option<ErrorInfo>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub metadata: Option<HashMap<String, Value>>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ErrorInfo {
58 pub code: String,
59 pub message: String,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub details: Option<Value>,
63}
64
65impl ApiResponse {
66 pub fn from_ocr_result(result: OcrResult) -> Self {
68 Self {
69 request_id: result.request_id,
70 version: result.version,
71 image_width: result.image_width,
72 image_height: result.image_height,
73 is_printed: result.is_printed,
74 is_handwritten: result.is_handwritten,
75 auto_rotate_confidence: Some(result.auto_rotate_confidence),
76 auto_rotate_degrees: Some(result.auto_rotate_degrees),
77 confidence: result.confidence,
78 confidence_rate: result.confidence_rate,
79 formats: result.formats,
80 line_data: result.line_data,
81 error: result.error,
82 error_info: None,
83 metadata: if result.metadata.is_empty() {
84 None
85 } else {
86 Some(result.metadata)
87 },
88 }
89 }
90
91 pub fn error(request_id: String, code: &str, message: &str) -> Self {
93 Self {
94 request_id,
95 version: "3.0".to_string(),
96 image_width: 0,
97 image_height: 0,
98 is_printed: false,
99 is_handwritten: false,
100 auto_rotate_confidence: None,
101 auto_rotate_degrees: None,
102 confidence: 0.0,
103 confidence_rate: 0.0,
104 formats: FormatsData::default(),
105 line_data: None,
106 error: Some(message.to_string()),
107 error_info: Some(ErrorInfo {
108 code: code.to_string(),
109 message: message.to_string(),
110 details: None,
111 }),
112 metadata: None,
113 }
114 }
115
116 pub fn to_json(&self) -> Result<String, String> {
118 serde_json::to_string(self)
119 .map_err(|e| format!("JSON serialization error: {}", e))
120 }
121
122 pub fn to_json_pretty(&self) -> Result<String, String> {
124 serde_json::to_string_pretty(self)
125 .map_err(|e| format!("JSON serialization error: {}", e))
126 }
127
128 pub fn from_json(json: &str) -> Result<Self, String> {
130 serde_json::from_str(json)
131 .map_err(|e| format!("JSON parsing error: {}", e))
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct BatchApiResponse {
138 pub batch_id: String,
139 pub total: usize,
140 pub completed: usize,
141 pub results: Vec<ApiResponse>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub errors: Option<Vec<BatchError>>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct BatchError {
149 pub index: usize,
150 pub error: ErrorInfo,
151}
152
153impl BatchApiResponse {
154 pub fn new(batch_id: String, results: Vec<ApiResponse>) -> Self {
155 let total = results.len();
156 let completed = results.iter().filter(|r| r.error.is_none()).count();
157
158 let errors: Vec<BatchError> = results
159 .iter()
160 .enumerate()
161 .filter_map(|(i, r)| {
162 r.error_info.as_ref().map(|e| BatchError {
163 index: i,
164 error: e.clone(),
165 })
166 })
167 .collect();
168
169 Self {
170 batch_id,
171 total,
172 completed,
173 results,
174 errors: if errors.is_empty() { None } else { Some(errors) },
175 }
176 }
177
178 pub fn to_json(&self) -> Result<String, String> {
179 serde_json::to_string(self)
180 .map_err(|e| format!("JSON serialization error: {}", e))
181 }
182
183 pub fn to_json_pretty(&self) -> Result<String, String> {
184 serde_json::to_string_pretty(self)
185 .map_err(|e| format!("JSON serialization error: {}", e))
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ApiRequest {
192 pub src: String,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub formats: Option<Vec<String>>,
198
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub ocr: Option<OcrOptions>,
202
203 #[serde(flatten)]
205 pub metadata: HashMap<String, Value>,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct OcrOptions {
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub math_inline_delimiters: Option<Vec<String>>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub math_display_delimiters: Option<Vec<String>>,
215
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub rm_spaces: Option<bool>,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub rm_fonts: Option<bool>,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub numbers_default_to_math: Option<bool>,
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 fn create_test_result() -> OcrResult {
231 OcrResult {
232 request_id: "test_123".to_string(),
233 version: "3.0".to_string(),
234 image_width: 800,
235 image_height: 600,
236 is_printed: true,
237 is_handwritten: false,
238 auto_rotate_confidence: 0.95,
239 auto_rotate_degrees: 0,
240 confidence: 0.98,
241 confidence_rate: 0.97,
242 formats: FormatsData {
243 text: Some("E = mc^2".to_string()),
244 latex_normal: Some(r"E = mc^2".to_string()),
245 ..Default::default()
246 },
247 line_data: None,
248 error: None,
249 metadata: HashMap::new(),
250 }
251 }
252
253 #[test]
254 fn test_api_response_from_result() {
255 let result = create_test_result();
256 let response = ApiResponse::from_ocr_result(result);
257
258 assert_eq!(response.request_id, "test_123");
259 assert_eq!(response.version, "3.0");
260 assert_eq!(response.confidence, 0.98);
261 assert!(response.formats.text.is_some());
262 }
263
264 #[test]
265 fn test_api_response_to_json() {
266 let result = create_test_result();
267 let response = ApiResponse::from_ocr_result(result);
268 let json = response.to_json().unwrap();
269
270 assert!(json.contains("request_id"));
271 assert!(json.contains("test_123"));
272 assert!(json.contains("confidence"));
273 }
274
275 #[test]
276 fn test_api_response_round_trip() {
277 let result = create_test_result();
278 let response = ApiResponse::from_ocr_result(result);
279 let json = response.to_json().unwrap();
280 let parsed = ApiResponse::from_json(&json).unwrap();
281
282 assert_eq!(response.request_id, parsed.request_id);
283 assert_eq!(response.confidence, parsed.confidence);
284 }
285
286 #[test]
287 fn test_error_response() {
288 let response = ApiResponse::error(
289 "test_456".to_string(),
290 "invalid_image",
291 "Image format not supported"
292 );
293
294 assert_eq!(response.request_id, "test_456");
295 assert!(response.error.is_some());
296 assert!(response.error_info.is_some());
297
298 let error_info = response.error_info.unwrap();
299 assert_eq!(error_info.code, "invalid_image");
300 }
301
302 #[test]
303 fn test_batch_response() {
304 let result1 = create_test_result();
305 let result2 = create_test_result();
306
307 let responses = vec![
308 ApiResponse::from_ocr_result(result1),
309 ApiResponse::from_ocr_result(result2),
310 ];
311
312 let batch = BatchApiResponse::new("batch_789".to_string(), responses);
313
314 assert_eq!(batch.batch_id, "batch_789");
315 assert_eq!(batch.total, 2);
316 assert_eq!(batch.completed, 2);
317 assert!(batch.errors.is_none());
318 }
319
320 #[test]
321 fn test_batch_with_errors() {
322 let success = create_test_result();
323 let error_response = ApiResponse::error(
324 "fail_1".to_string(),
325 "timeout",
326 "Processing timeout"
327 );
328
329 let responses = vec![
330 ApiResponse::from_ocr_result(success),
331 error_response,
332 ];
333
334 let batch = BatchApiResponse::new("batch_error".to_string(), responses);
335
336 assert_eq!(batch.total, 2);
337 assert_eq!(batch.completed, 1);
338 assert!(batch.errors.is_some());
339 assert_eq!(batch.errors.unwrap().len(), 1);
340 }
341
342 #[test]
343 fn test_api_request() {
344 let request = ApiRequest {
345 src: "https://example.com/image.png".to_string(),
346 formats: Some(vec!["text".to_string(), "latex_styled".to_string()]),
347 ocr: Some(OcrOptions {
348 math_inline_delimiters: Some(vec!["$".to_string(), "$".to_string()]),
349 math_display_delimiters: Some(vec!["$$".to_string(), "$$".to_string()]),
350 rm_spaces: Some(true),
351 rm_fonts: None,
352 numbers_default_to_math: Some(false),
353 }),
354 metadata: HashMap::new(),
355 };
356
357 let json = serde_json::to_string(&request).unwrap();
358 assert!(json.contains("src"));
359 assert!(json.contains("formats"));
360 }
361}