Skip to main content

zai_rs/model/ocr/
request.rs

1use serde::Serialize;
2use validator::Validate;
3
4/// OCR tool types
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
6pub enum OcrToolType {
7    /// Handwriting recognition.
8    #[serde(rename = "hand_write")]
9    HandWrite,
10}
11
12impl OcrToolType {
13    /// Return the exact multipart form value expected by the OCR endpoint.
14    pub const fn as_str(self) -> &'static str {
15        match self {
16            Self::HandWrite => "hand_write",
17        }
18    }
19}
20
21/// Language types for OCR recognition
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
23pub enum OcrLanguageType {
24    /// Auto-detect language
25    #[serde(rename = "AUTO")]
26    Auto,
27
28    /// Chinese and English (default)
29    #[serde(rename = "CHN_ENG")]
30    ChnEng,
31
32    /// English
33    #[serde(rename = "ENG")]
34    Eng,
35
36    /// Japanese
37    #[serde(rename = "JAP")]
38    Jap,
39
40    /// Korean
41    #[serde(rename = "KOR")]
42    Kor,
43
44    /// French
45    #[serde(rename = "FRE")]
46    Fre,
47
48    /// Spanish
49    #[serde(rename = "SPA")]
50    Spa,
51
52    /// Portuguese
53    #[serde(rename = "POR")]
54    Por,
55
56    /// German
57    #[serde(rename = "GER")]
58    Ger,
59
60    /// Italian
61    #[serde(rename = "ITA")]
62    Ita,
63
64    /// Russian
65    #[serde(rename = "RUS")]
66    Rus,
67
68    /// Danish
69    #[serde(rename = "DAN")]
70    Dan,
71
72    /// Dutch
73    #[serde(rename = "DUT")]
74    Dut,
75
76    /// Malay
77    #[serde(rename = "MAL")]
78    Mal,
79
80    /// Swedish
81    #[serde(rename = "SWE")]
82    Swe,
83
84    /// Indonesian
85    #[serde(rename = "IND")]
86    Ind,
87
88    /// Polish
89    #[serde(rename = "POL")]
90    Pol,
91
92    /// Romanian
93    #[serde(rename = "ROM")]
94    Rom,
95
96    /// Turkish
97    #[serde(rename = "TUR")]
98    Tur,
99
100    /// Greek
101    #[serde(rename = "GRE")]
102    Gre,
103
104    /// Hungarian
105    #[serde(rename = "HUN")]
106    Hun,
107
108    /// Thai
109    #[serde(rename = "THA")]
110    Tha,
111
112    /// Vietnamese
113    #[serde(rename = "VIE")]
114    Vie,
115
116    /// Arabic
117    #[serde(rename = "ARA")]
118    Ara,
119
120    /// Hindi
121    #[serde(rename = "HIN")]
122    Hin,
123}
124
125impl OcrLanguageType {
126    /// Return the exact multipart form value expected by the OCR endpoint.
127    pub const fn as_str(self) -> &'static str {
128        match self {
129            Self::Auto => "AUTO",
130            Self::ChnEng => "CHN_ENG",
131            Self::Eng => "ENG",
132            Self::Jap => "JAP",
133            Self::Kor => "KOR",
134            Self::Fre => "FRE",
135            Self::Spa => "SPA",
136            Self::Por => "POR",
137            Self::Ger => "GER",
138            Self::Ita => "ITA",
139            Self::Rus => "RUS",
140            Self::Dan => "DAN",
141            Self::Dut => "DUT",
142            Self::Mal => "MAL",
143            Self::Swe => "SWE",
144            Self::Ind => "IND",
145            Self::Pol => "POL",
146            Self::Rom => "ROM",
147            Self::Tur => "TUR",
148            Self::Gre => "GRE",
149            Self::Hun => "HUN",
150            Self::Tha => "THA",
151            Self::Vie => "VIE",
152            Self::Ara => "ARA",
153            Self::Hin => "HIN",
154        }
155    }
156}
157
158/// Body parameters holder for OCR request (used to build multipart form)
159#[derive(Clone, Serialize, Validate)]
160pub struct OcrBody {
161    /// Tool type (fixed as "hand_write" for handwriting recognition)
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub tool_type: Option<OcrToolType>,
164
165    /// Language type for recognition (default: CHN_ENG)
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub language_type: Option<OcrLanguageType>,
168
169    /// Whether to return confidence information
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub probability: Option<bool>,
172
173    /// Client-provided unique request id
174    #[serde(skip_serializing_if = "Option::is_none")]
175    #[validate(length(min = 6, max = 64))]
176    pub request_id: Option<String>,
177
178    /// End user id (6..=128 chars)
179    #[serde(skip_serializing_if = "Option::is_none")]
180    #[validate(length(min = 6, max = 128))]
181    pub user_id: Option<String>,
182}
183
184impl std::fmt::Debug for OcrBody {
185    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        formatter
187            .debug_struct("OcrBody")
188            .field("tool_type", &self.tool_type)
189            .field("language_type", &self.language_type)
190            .field("probability", &self.probability)
191            .field(
192                "request_id",
193                &self.request_id.as_ref().map(|_| "[REDACTED]"),
194            )
195            .field("user_id", &self.user_id.as_ref().map(|_| "[REDACTED]"))
196            .finish()
197    }
198}
199
200impl Default for OcrBody {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206impl OcrBody {
207    /// Create a new empty OCR body (all fields `None`).
208    pub fn new() -> Self {
209        Self {
210            tool_type: None,
211            language_type: None,
212            probability: None,
213            request_id: None,
214            user_id: None,
215        }
216    }
217
218    /// Set the OCR tool type.
219    pub fn with_tool_type(mut self, tool_type: OcrToolType) -> Self {
220        self.tool_type = Some(tool_type);
221        self
222    }
223
224    /// Set the document language.
225    pub fn with_language_type(mut self, language_type: OcrLanguageType) -> Self {
226        self.language_type = Some(language_type);
227        self
228    }
229
230    /// Whether to return per-character recognition probabilities.
231    pub fn with_probability(mut self, probability: bool) -> Self {
232        self.probability = Some(probability);
233        self
234    }
235
236    /// Set the client-side request id.
237    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
238        self.request_id = Some(request_id.into());
239        self
240    }
241
242    /// Set the end-user id (6..=128 chars).
243    pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
244        self.user_id = Some(user_id.into());
245        self
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn debug_redacts_request_and_user_identifiers() {
255        let body = OcrBody::new()
256            .with_request_id("private-request")
257            .with_user_id("private-user");
258        let debug = format!("{body:?}");
259        assert!(!debug.contains("private-request"));
260        assert!(!debug.contains("private-user"));
261    }
262}