Skip to main content

cloudreve_api/api/v4/models/
common.rs

1//! Common types for Cloudreve API v4
2
3use crate::Error;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Business code returned when a batch operation only partially succeeded.
8///
9/// The server aggregates per-item failures into `aggregated_error` and reports
10/// this code at the top level. Items missing from that map succeeded.
11pub const CODE_BATCH_OPERATION_NOT_FULLY_COMPLETED: i32 = 40081;
12
13/// Business code returned when a target file is locked by another session.
14///
15/// At the top level the lock details land in `data`; inside `aggregated_error`
16/// they land in the sub-item's own `data`.
17pub const CODE_LOCK_CONFLICT: i32 = 40073;
18
19/// A single item's failure inside [`ApiResponse::aggregated_error`].
20///
21/// The server serializes each failed item as a full response object, so a
22/// sub-item carries its own business code and payload — a lock conflict, for
23/// example, arrives as `code: 40073` with the unlock tokens in `data`.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AggregatedItemError {
26    pub code: i32,
27    #[serde(default)]
28    pub msg: String,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub data: Option<serde_json::Value>,
31}
32
33/// Generic API response wrapper
34#[derive(Debug, Serialize, Deserialize)]
35pub struct ApiResponse<T> {
36    pub code: i32,
37    pub msg: String,
38    pub data: Option<T>,
39    /// Per-item failures for batch operations, keyed by the URI that was sent.
40    ///
41    /// Only present when the operation partially failed (see
42    /// [`CODE_BATCH_OPERATION_NOT_FULLY_COMPLETED`]). Items that succeeded are
43    /// absent from the map, so the successful set is the request's URIs minus
44    /// these keys.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub aggregated_error: Option<HashMap<String, AggregatedItemError>>,
47}
48
49impl ApiResponse<serde_json::Value> {
50    /// Convert a non-zero response into the most informative error variant.
51    ///
52    /// Folding every failure into [`Error::Api`] would discard exactly what
53    /// callers need to act on: which items of a batch failed, and the unlock
54    /// tokens behind a lock conflict. Endpoints that can return either should
55    /// route their errors through here.
56    pub fn into_error(self) -> Error {
57        if let Some(errors) = self.aggregated_error {
58            return Error::Aggregate {
59                code: self.code,
60                message: self.msg,
61                errors,
62            };
63        }
64        match self.data {
65            Some(data) if !data.is_null() => Error::ApiWithData {
66                code: self.code,
67                message: self.msg,
68                data,
69            },
70            _ => Error::Api {
71                code: self.code,
72                message: self.msg,
73            },
74        }
75    }
76}