ruvector_scipix/api/
requests.rs

1use serde::{Deserialize, Serialize};
2use validator::Validate;
3
4/// Text/Image OCR request
5#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
6pub struct TextRequest {
7    /// Image source (base64, URL, or multipart)
8    #[serde(skip_serializing_if = "Option::is_none")]
9    pub src: Option<String>,
10
11    /// Base64 encoded image data
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub base64: Option<String>,
14
15    /// Image URL
16    #[serde(skip_serializing_if = "Option::is_none")]
17    #[validate(url)]
18    pub url: Option<String>,
19
20    /// Request metadata
21    #[serde(default)]
22    pub metadata: RequestMetadata,
23}
24
25impl TextRequest {
26    /// Get image data from request
27    pub async fn get_image_data(&self) -> anyhow::Result<Vec<u8>> {
28        if let Some(base64_data) = &self.base64 {
29            // Decode base64
30            use base64::Engine;
31            let decoded = base64::engine::general_purpose::STANDARD.decode(base64_data)?;
32            Ok(decoded)
33        } else if let Some(url) = &self.url {
34            // Download from URL
35            let response = reqwest::get(url).await?;
36            let bytes = response.bytes().await?;
37            Ok(bytes.to_vec())
38        } else {
39            anyhow::bail!("No image data provided")
40        }
41    }
42}
43
44/// Digital ink strokes request
45#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
46pub struct StrokesRequest {
47    /// Array of stroke data
48    #[validate(length(min = 1))]
49    pub strokes: Vec<Stroke>,
50
51    /// Request metadata
52    #[serde(default)]
53    pub metadata: RequestMetadata,
54}
55
56/// Single stroke in digital ink
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Stroke {
59    /// X coordinates
60    pub x: Vec<f64>,
61
62    /// Y coordinates
63    pub y: Vec<f64>,
64
65    /// Optional timestamps
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub t: Option<Vec<f64>>,
68}
69
70/// Legacy LaTeX equation request
71#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
72pub struct LatexRequest {
73    /// Image source
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub src: Option<String>,
76
77    /// Base64 encoded image
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub base64: Option<String>,
80
81    /// Image URL
82    #[serde(skip_serializing_if = "Option::is_none")]
83    #[validate(url)]
84    pub url: Option<String>,
85
86    /// Request metadata
87    #[serde(default)]
88    pub metadata: RequestMetadata,
89}
90
91/// PDF processing request
92#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
93pub struct PdfRequest {
94    /// PDF file URL
95    #[validate(url)]
96    pub url: String,
97
98    /// Conversion options
99    #[serde(default)]
100    pub options: PdfOptions,
101
102    /// Webhook URL for completion notification
103    #[serde(skip_serializing_if = "Option::is_none")]
104    #[validate(url)]
105    pub webhook_url: Option<String>,
106
107    /// Request metadata
108    #[serde(default)]
109    pub metadata: RequestMetadata,
110}
111
112/// PDF processing options
113#[derive(Debug, Clone, Serialize, Deserialize, Default)]
114pub struct PdfOptions {
115    /// Output format
116    #[serde(default = "default_format")]
117    pub format: String,
118
119    /// Enable OCR
120    #[serde(default)]
121    pub enable_ocr: bool,
122
123    /// Include images
124    #[serde(default = "default_true")]
125    pub include_images: bool,
126
127    /// Page range (e.g., "1-5")
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub page_range: Option<String>,
130}
131
132fn default_format() -> String {
133    "mmd".to_string()
134}
135
136fn default_true() -> bool {
137    true
138}
139
140/// Request metadata
141#[derive(Debug, Clone, Serialize, Deserialize, Default)]
142pub struct RequestMetadata {
143    /// Output formats
144    #[serde(default = "default_formats")]
145    pub formats: Vec<String>,
146
147    /// Include confidence scores
148    #[serde(default)]
149    pub include_confidence: bool,
150
151    /// Enable math mode
152    #[serde(default = "default_true")]
153    pub enable_math: bool,
154
155    /// Language hint
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub language: Option<String>,
158}
159
160fn default_formats() -> Vec<String> {
161    vec!["text".to_string()]
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_text_request_validation() {
170        let request = TextRequest {
171            src: None,
172            base64: Some("SGVsbG8gV29ybGQ=".to_string()),
173            url: None,
174            metadata: RequestMetadata::default(),
175        };
176
177        assert!(request.validate().is_ok());
178    }
179
180    #[test]
181    fn test_strokes_request_validation() {
182        let request = StrokesRequest {
183            strokes: vec![Stroke {
184                x: vec![0.0, 1.0, 2.0],
185                y: vec![0.0, 1.0, 0.0],
186                t: None,
187            }],
188            metadata: RequestMetadata::default(),
189        };
190
191        assert!(request.validate().is_ok());
192    }
193
194    #[test]
195    fn test_empty_strokes_validation() {
196        let request = StrokesRequest {
197            strokes: vec![],
198            metadata: RequestMetadata::default(),
199        };
200
201        assert!(request.validate().is_err());
202    }
203
204    #[test]
205    fn test_pdf_request_validation() {
206        let request = PdfRequest {
207            url: "https://example.com/document.pdf".to_string(),
208            options: PdfOptions::default(),
209            webhook_url: None,
210            metadata: RequestMetadata::default(),
211        };
212
213        assert!(request.validate().is_ok());
214    }
215
216    #[test]
217    fn test_invalid_url() {
218        let request = PdfRequest {
219            url: "not-a-url".to_string(),
220            options: PdfOptions::default(),
221            webhook_url: None,
222            metadata: RequestMetadata::default(),
223        };
224
225        assert!(request.validate().is_err());
226    }
227}