capmonstercloud_client/requests/
recognition_complex_image.rs1use serde::Serialize;
2
3use super::complex_image::timeouts;
4use super::CaptchaRequest;
5use crate::error::{Error, Result};
6use crate::timeouts::GetResultTimeouts;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
10pub enum PayloadType {
11 Audio,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
17pub struct RecognitionMetadata {
18 #[serde(rename = "Task")]
23 pub task: String,
24 #[serde(rename = "TaskArgument", skip_serializing_if = "Option::is_none")]
27 pub task_argument: Option<String>,
28 #[serde(rename = "PayloadType", skip_serializing_if = "Option::is_none")]
30 pub payload_type: Option<PayloadType>,
31}
32
33impl RecognitionMetadata {
34 pub fn new(task: impl Into<String>) -> Self {
35 Self {
36 task: task.into(),
37 task_argument: None,
38 payload_type: None,
39 }
40 }
41
42 pub fn with_task_argument(mut self, value: impl Into<String>) -> Self {
43 self.task_argument = Some(value.into());
44 self
45 }
46
47 pub fn with_payload_type(mut self, value: PayloadType) -> Self {
48 self.payload_type = Some(value);
49 self
50 }
51}
52
53#[derive(Debug, Clone, Serialize)]
61pub struct RecognitionComplexImageTaskRequest {
62 #[serde(rename = "imagesBase64")]
64 pub images_base64: Vec<String>,
65 pub metadata: RecognitionMetadata,
67 #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
69 pub user_agent: Option<String>,
70}
71
72impl RecognitionComplexImageTaskRequest {
73 pub fn new(images_base64: Vec<String>, metadata: RecognitionMetadata) -> Self {
74 Self {
75 images_base64,
76 metadata,
77 user_agent: None,
78 }
79 }
80
81 pub fn single(image_base64: impl Into<String>, metadata: RecognitionMetadata) -> Self {
83 Self::new(vec![image_base64.into()], metadata)
84 }
85
86 pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
87 self.user_agent = Some(value.into());
88 self
89 }
90}
91
92impl CaptchaRequest for RecognitionComplexImageTaskRequest {
93 const TASK_TYPE: &'static str = "ComplexImageTask";
94 const CLASS: Option<&'static str> = Some("recognition");
95
96 fn timeouts(&self) -> GetResultTimeouts {
97 timeouts()
98 }
99
100 fn validate(&self) -> Result<()> {
101 if self.metadata.task.is_empty() {
102 return Err(Error::Validation("metadata: Task must be defined".into()));
103 }
104 if self.images_base64.is_empty() {
105 return Err(Error::Validation(
106 "images_base64: at least one image expected".into(),
107 ));
108 }
109 Ok(())
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use serde_json::json;
117
118 #[test]
119 fn minimal_task() {
120 let task =
121 RecognitionComplexImageTaskRequest::single("AAA", RecognitionMetadata::new("shein"))
122 .to_task()
123 .unwrap();
124 assert_eq!(
125 task,
126 json!({"type": "ComplexImageTask", "class": "recognition",
127 "imagesBase64": ["AAA"], "metadata": {"Task": "shein"}})
128 );
129 }
130
131 #[test]
132 fn full_task() {
133 let meta = RecognitionMetadata::new("bills_audio")
134 .with_task_argument("123")
135 .with_payload_type(PayloadType::Audio);
136 let task = RecognitionComplexImageTaskRequest::new(vec!["A".into(), "B".into()], meta)
137 .with_user_agent("UA")
138 .to_task()
139 .unwrap();
140 assert_eq!(
141 task,
142 json!({"type": "ComplexImageTask", "class": "recognition",
143 "imagesBase64": ["A", "B"],
144 "metadata": {"Task": "bills_audio", "TaskArgument": "123", "PayloadType": "Audio"},
145 "userAgent": "UA"})
146 );
147 }
148
149 #[test]
150 fn validation_errors() {
151 let no_images =
152 RecognitionComplexImageTaskRequest::new(vec![], RecognitionMetadata::new("shein"));
153 assert!(matches!(no_images.to_task(), Err(Error::Validation(_))));
154 let no_task = RecognitionComplexImageTaskRequest::single("A", RecognitionMetadata::new(""));
155 assert!(matches!(no_task.to_task(), Err(Error::Validation(_))));
156 }
157}