1use serde::{Deserialize, Serialize};
2use validator::Validate;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(rename_all = "snake_case")]
7pub enum SearchEngine {
8 SearchStd,
10 SearchPro,
12 SearchProSogou,
14 SearchProQuark,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20#[serde(rename_all = "snake_case")]
21pub enum SearchRecencyFilter {
22 OneDay,
24 OneWeek,
26 OneMonth,
28 OneYear,
30 NoLimit,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
36#[serde(rename_all = "snake_case")]
37pub enum ContentSize {
38 Medium,
40 High,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SearchIntent {
47 pub query: String,
49 pub intent: String,
51 pub keywords: String,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SearchResult {
58 pub title: String,
60 pub content: String,
62 pub link: String,
64 pub media: String,
66 pub icon: String,
68 pub refer: String,
70 pub publish_date: String,
72}
73
74#[derive(Debug, Clone, Serialize, Validate)]
76pub struct WebSearchBody {
77 #[validate(length(max = 70, message = "search_query cannot exceed 70 characters"))]
79 pub search_query: String,
80
81 pub search_engine: SearchEngine,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub search_intent: Option<bool>,
87
88 #[validate(range(min = 1, max = 50, message = "count must be between 1 and 50"))]
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub count: Option<i32>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub search_domain_filter: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub search_recency_filter: Option<SearchRecencyFilter>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub content_size: Option<ContentSize>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub request_id: Option<String>,
108
109 #[validate(length(
111 min = 6,
112 max = 128,
113 message = "user_id must be between 6 and 128 characters"
114 ))]
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub user_id: Option<String>,
117}
118
119impl WebSearchBody {
120 pub fn new(search_query: String, search_engine: SearchEngine) -> Self {
122 Self {
123 search_query,
124 search_engine,
125 search_intent: None,
126 count: None,
127 search_domain_filter: None,
128 search_recency_filter: None,
129 content_size: None,
130 request_id: None,
131 user_id: None,
132 }
133 }
134
135 pub fn with_search_intent(mut self, enabled: bool) -> Self {
137 self.search_intent = Some(enabled);
138 self
139 }
140
141 pub fn with_count(mut self, count: i32) -> Self {
143 self.count = Some(count);
144 self
145 }
146
147 pub fn with_domain_filter(mut self, domain: String) -> Self {
149 self.search_domain_filter = Some(domain);
150 self
151 }
152
153 pub fn with_recency_filter(mut self, filter: SearchRecencyFilter) -> Self {
155 self.search_recency_filter = Some(filter);
156 self
157 }
158
159 pub fn with_content_size(mut self, size: ContentSize) -> Self {
161 self.content_size = Some(size);
162 self
163 }
164
165 pub fn with_request_id(mut self, request_id: String) -> Self {
167 self.request_id = Some(request_id);
168 self
169 }
170
171 pub fn with_user_id(mut self, user_id: String) -> Self {
173 self.user_id = Some(user_id);
174 self
175 }
176
177 pub fn validate_constraints(&self) -> crate::ZaiResult<()> {
179 self.validate()
180 .map_err(|e| crate::client::error::ZaiError::ApiError {
181 code: 1200,
182 message: format!("Validation error: {}", e),
183 })?;
184
185 if let Some(count) = self.count
187 && matches!(self.search_engine, SearchEngine::SearchProSogou)
188 {
189 match count {
190 10 | 20 | 30 | 40 | 50 => {},
191 _ => {
192 return Err(crate::client::error::ZaiError::ApiError {
193 code: 1200,
194 message: "search_pro_sogou only supports count values: 10, 20, 30, 40, 50"
195 .to_string(),
196 });
197 },
198 }
199 }
200
201 Ok(())
202 }
203}