anycms-core 0.3.0

A unified API response library supporting multiple Rust web frameworks
Documentation
use crate::pagination::ResultPagination;
use serde::{Deserialize, Serialize};
use serde_json::Value;

/// API response wrapper with unified structure
///
/// # Fields
/// - `success`: Indicates if the request was successful
/// - `data`: Contains a single value (mutually exclusive with `list`)
/// - `list`: Contains a list of values (mutually exclusive with `data`)
/// - `message`: Optional message or error description
/// - `code`: Optional error code
/// - `pagination`: Optional pagination metadata for list responses
/// - `extra`: Additional metadata as key-value pairs
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiResult<T> {
    pub success: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<T>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub list: Option<Vec<T>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pagination: Option<ResultPagination>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extra: Option<Value>,
}

impl<T> ApiResult<T> {
    /// Create a successful response with a single value
    pub fn value(v: T) -> Self {
        ApiResult {
            success: true,
            data: Some(v),
            list: None,
            message: None,
            code: None,
            pagination: None,
            extra: None,
        }
    }

    /// Create a successful response with a list of values
    pub fn list(v: Vec<T>) -> Self {
        ApiResult {
            success: true,
            data: None,
            list: Some(v),
            message: None,
            code: None,
            pagination: None,
            extra: None,
        }
    }

    /// Create a failed response with a message (alias for `failure`)
    pub fn fail(message: &str) -> Self {
        Self::failure(message)
    }

    /// Create a failed response with a message
    pub fn failure(message: &str) -> Self {
        ApiResult {
            success: false,
            data: None,
            list: None,
            message: Some(message.to_string()),
            code: None,
            pagination: None,
            extra: None,
        }
    }

    /// Create a successful response without data
    pub fn success() -> Self {
        ApiResult {
            success: true,
            data: None,
            list: None,
            message: None,
            code: None,
            pagination: None,
            extra: None,
        }
    }

    /// Add extra metadata to the response
    ///
    /// # Example
    /// ```rust
    /// let result = ApiResult::success()
    ///     .with_extra("timestamp", json!(1234567890))
    ///     .with_extra("version", json!("1.0.0"));
    /// ```
    pub fn with_extra(mut self, key: &str, value: Value) -> Self {
        match self.extra {
            Some(ref mut v) => {
                v[key] = value;
            }
            None => {
                let mut v = serde_json::Map::new();
                v.insert(key.to_string(), value);
                self.extra = Some(v.into());
            }
        }
        self
    }

    /// Set error code for the response
    pub fn with_code(mut self, code: i32) -> Self {
        self.code = Some(code);
        self
    }

    /// Set pagination metadata for list responses
    pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
        self.pagination = Some(pagination);
        self
    }

    /// Set message for the response
    pub fn with_message(mut self, message: &str) -> Self {
        self.message = Some(message.to_string());
        self
    }
}

/// Conversion into Result for convenient error handling
impl<T, E> Into<Result<ApiResult<T>, E>> for ApiResult<T> {
    fn into(self) -> Result<ApiResult<T>, E> {
        Ok(self)
    }
}

/// Type alias for standard Result with Box<dyn Error>
pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;

/// Type alias for Result with anyhow::Error
pub type AnyhowResult<T> = Result<T, anyhow::Error>;

/// API result without any data payload
///
/// Use this for endpoints that only return success/failure status
/// or endpoints that only use `extra` metadata without structured data.
///
/// # Example
/// ```rust
/// // Simple success response
/// ApiResult::<()>::success()
///
/// // Error response
/// ApiResult::<()>::failure("Operation failed")
/// ```
pub type EmptyResult = ApiResult<()>;