Skip to main content

zai_rs/knowledge/
types.rs

1use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
2use validator::Validate;
3
4/// Standard knowledge API success envelope.
5///
6/// The frozen schemas make every top-level field optional. Deserialization
7/// nevertheless rejects `{}` and all-null payloads so an unrelated success
8/// body cannot be mistaken for a valid knowledge response.
9#[derive(Debug, Clone, Serialize, Validate)]
10pub struct KnowledgeResponse<T> {
11    /// Endpoint-specific response payload.
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub data: Option<T>,
14    /// Business status code, when returned. The shared transport rejects an
15    /// explicitly non-success code before this type is returned.
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub code: Option<i64>,
18    /// Human-readable status message.
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub message: Option<String>,
21    /// Server timestamp.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub timestamp: Option<u64>,
24}
25
26#[derive(Deserialize)]
27struct KnowledgeResponseWire<T> {
28    data: Option<T>,
29    code: Option<i64>,
30    message: Option<String>,
31    timestamp: Option<u64>,
32}
33
34impl<'de, T> Deserialize<'de> for KnowledgeResponse<T>
35where
36    T: Deserialize<'de>,
37{
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: Deserializer<'de>,
41    {
42        let wire = KnowledgeResponseWire::deserialize(deserializer)?;
43        if wire.data.is_none()
44            && wire.code.is_none()
45            && wire.message.is_none()
46            && wire.timestamp.is_none()
47        {
48            return Err(D::Error::custom(
49                "knowledge response contained no documented non-null fields",
50            ));
51        }
52        Ok(Self {
53            data: wire.data,
54            code: wire.code,
55            message: wire.message,
56            timestamp: wire.timestamp,
57        })
58    }
59}
60
61/// Standard knowledge API response envelope for operations without a payload.
62#[derive(Debug, Clone, Serialize, Validate)]
63pub struct KnowledgeOperationResponse {
64    /// Business status code, when the endpoint includes it. The shared
65    /// transport rejects an explicitly non-success value before this type is
66    /// returned.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub code: Option<i64>,
69    /// Human-readable status message.
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub message: Option<String>,
72    /// Server timestamp.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub timestamp: Option<u64>,
75}
76
77#[derive(Deserialize)]
78struct KnowledgeOperationResponseWire {
79    code: Option<i64>,
80    message: Option<String>,
81    timestamp: Option<u64>,
82}
83
84impl<'de> Deserialize<'de> for KnowledgeOperationResponse {
85    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86    where
87        D: Deserializer<'de>,
88    {
89        let wire = KnowledgeOperationResponseWire::deserialize(deserializer)?;
90        if wire.code.is_none() && wire.message.is_none() && wire.timestamp.is_none() {
91            return Err(D::Error::custom(
92                "knowledge operation response contained no documented non-null fields",
93            ));
94        }
95        Ok(Self {
96            code: wire.code,
97            message: wire.message,
98            timestamp: wire.timestamp,
99        })
100    }
101}
102
103/// Knowledge base item
104#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
105pub struct KnowledgeItem {
106    /// Knowledge base id
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub id: Option<String>,
109    /// Embedding model id
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub embedding_id: Option<u64>,
112    /// Whether contextual retrieval is enabled (`0` or `1`).
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub contextual: Option<u8>,
115    /// Knowledge base name
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub name: Option<String>,
118    /// Knowledge base description
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub description: Option<String>,
121    /// Background color
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub background: Option<String>,
124    /// Icon URL
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub icon: Option<String>,
127    /// Number of documents
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub document_size: Option<u64>,
130    /// Total tokenized length
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub length: Option<u64>,
133    /// Total words
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub word_num: Option<u64>,
136}
137
138/// Knowledge list data payload
139#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
140pub struct KnowledgeListData {
141    /// Knowledge list
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub list: Option<Vec<KnowledgeItem>>,
144    /// Total count
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub total: Option<u64>,
147}
148
149/// Knowledge list response envelope.
150pub type KnowledgeListResponse = KnowledgeResponse<KnowledgeListData>;
151
152/// Knowledge detail response envelope (data is a single item).
153pub type KnowledgeGetResponse = KnowledgeResponse<KnowledgeItem>;
154
155/// Capacity usage counters
156#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
157pub struct KnowledgeUsageCounts {
158    /// Total words
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub word_num: Option<u64>,
161    /// Total bytes (length)
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub length: Option<u64>,
164}
165
166/// Capacity data payload
167#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
168pub struct KnowledgeCapacityData {
169    /// Used usage
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub used: Option<KnowledgeUsageCounts>,
172    /// Total quota
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub total: Option<KnowledgeUsageCounts>,
175}
176
177/// Capacity response envelope.
178pub type KnowledgeCapacityResponse = KnowledgeResponse<KnowledgeCapacityData>;
179
180/// Document vectorization failure info
181#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
182pub struct DocumentFailInfo {
183    /// Embedding failure code
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub embedding_code: Option<i64>,
186    /// Embedding failure message
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub embedding_msg: Option<String>,
189}
190
191/// Document item in a knowledge base
192#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
193pub struct DocumentItem {
194    /// Document id
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub id: Option<String>,
197    /// Slice type (integer)
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub knowledge_type: Option<i64>,
200    /// Custom separators
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub custom_separator: Option<Vec<String>>,
203    /// Sentence size (slice size)
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub sentence_size: Option<u64>,
206    /// Document length (bytes)
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub length: Option<u64>,
209    /// Document words
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub word_num: Option<u64>,
212    /// Document name
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub name: Option<String>,
215    /// Document URL
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub url: Option<String>,
218    /// Embedding status (integer)
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub embedding_stat: Option<i64>,
221    /// Failure info (camelCase in API)
222    #[serde(rename = "failInfo", skip_serializing_if = "Option::is_none")]
223    pub fail_info: Option<DocumentFailInfo>,
224}
225
226/// Document detail response envelope (data is a single document item).
227pub type DocumentGetResponse = KnowledgeResponse<DocumentItem>;
228
229/// Inner data of [`DocumentListResponse`] — the document list and total count.
230#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
231pub struct DocumentListData {
232    /// Documents list
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub list: Option<Vec<DocumentItem>>,
235    /// Total count
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub total: Option<u64>,
238}
239
240/// Document list response envelope.
241pub type DocumentListResponse = KnowledgeResponse<DocumentListData>;
242
243/// Success info for URL upload
244#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
245pub struct DocumentUrlUploadSuccessInfo {
246    /// Created document id
247    #[serde(rename = "documentId", skip_serializing_if = "Option::is_none")]
248    pub document_id: Option<String>,
249    /// Source URL
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub url: Option<String>,
252}
253
254/// Failed info for URL upload
255#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
256pub struct DocumentUrlUploadFailedInfo {
257    /// Source URL
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub url: Option<String>,
260    /// Failure reason
261    #[serde(rename = "failReason", skip_serializing_if = "Option::is_none")]
262    pub fail_reason: Option<String>,
263}
264
265/// Upload URL response data payload
266#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
267pub struct DocumentUrlUploadData {
268    /// Success items
269    #[serde(rename = "successInfos", skip_serializing_if = "Option::is_none")]
270    pub success_infos: Option<Vec<DocumentUrlUploadSuccessInfo>>,
271    /// Failed items
272    #[serde(rename = "failedInfos", skip_serializing_if = "Option::is_none")]
273    pub failed_infos: Option<Vec<DocumentUrlUploadFailedInfo>>,
274}
275
276/// Upload-by-URL response envelope.
277pub type DocumentUrlUploadResponse = KnowledgeResponse<DocumentUrlUploadData>;
278
279/// Success info for file upload
280#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
281pub struct DocumentUploadSuccessInfo {
282    /// Created document id
283    #[serde(rename = "documentId", skip_serializing_if = "Option::is_none")]
284    pub document_id: Option<String>,
285    /// Original file name
286    #[serde(rename = "fileName", skip_serializing_if = "Option::is_none")]
287    pub file_name: Option<String>,
288}
289
290/// Failed info for file upload
291#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
292pub struct DocumentUploadFailedInfo {
293    /// Original file name
294    #[serde(rename = "fileName", skip_serializing_if = "Option::is_none")]
295    pub file_name: Option<String>,
296    /// Failure reason
297    #[serde(rename = "failReason", skip_serializing_if = "Option::is_none")]
298    pub fail_reason: Option<String>,
299}
300
301/// Upload file response data payload
302#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
303pub struct DocumentUploadData {
304    /// Success items
305    #[serde(rename = "successInfos", skip_serializing_if = "Option::is_none")]
306    pub success_infos: Option<Vec<DocumentUploadSuccessInfo>>,
307    /// Failed items
308    #[serde(rename = "failedInfos", skip_serializing_if = "Option::is_none")]
309    pub failed_infos: Option<Vec<DocumentUploadFailedInfo>>,
310}
311
312/// One parsed image mapping item
313#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
314pub struct DocumentImageItem {
315    /// Image index text, e.g. "【示意图序号_...】"
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub text: Option<String>,
318    /// Image URL
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub cos_url: Option<String>,
321}
322
323/// Image list data payload
324#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
325pub struct DocumentImageListData {
326    /// Images array
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub images: Option<Vec<DocumentImageItem>>,
329}
330
331/// Parsed-image list response envelope.
332pub type DocumentImageListResponse = KnowledgeResponse<DocumentImageListData>;
333
334/// File-upload response envelope.
335pub type DocumentUploadResponse = KnowledgeResponse<DocumentUploadData>;
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn success_envelopes_follow_optional_schema_and_reject_empty_success() {
343        assert!(
344            serde_json::from_value::<KnowledgeResponse<serde_json::Value>>(
345                serde_json::json!({"code": 200})
346            )
347            .is_ok()
348        );
349        assert!(
350            serde_json::from_value::<KnowledgeResponse<serde_json::Value>>(
351                serde_json::json!({"data": {}})
352            )
353            .is_ok()
354        );
355        assert!(
356            serde_json::from_value::<KnowledgeResponse<serde_json::Value>>(serde_json::json!({}))
357                .is_err()
358        );
359        assert!(
360            serde_json::from_value::<KnowledgeResponse<serde_json::Value>>(
361                serde_json::json!({"data": null, "code": null})
362            )
363            .is_err()
364        );
365        assert!(
366            serde_json::from_value::<KnowledgeOperationResponse>(serde_json::json!({
367                "unknown": "value"
368            }))
369            .is_err()
370        );
371        assert!(
372            serde_json::from_value::<KnowledgeOperationResponse>(serde_json::json!({
373                "message": "ok"
374            }))
375            .is_ok()
376        );
377    }
378}