tina-core 0.0.2

Tina platform
Documentation
//! 表格分页数据对象

use crate::tina::data::http_status::HttpStatus;
use crate::tina::data::no_data::NoData;
use crate::tina::data::return_code::{IReturnCode, IntoDynReturnCode};
use crate::tina::data::AppResult;
use crate::tina::data::{api_schema::ApiSchema, app_error::AppError};
use crate::tina::server::http::response::ResponseAttribute;
use crate::tina::server::session::Session;
use crate::tina::util::schema::SchemaExt;
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use std::fmt::Debug;
use std::sync::Arc;
use std::{any::type_name, borrow::Cow};
use utoipa::openapi::{
    path::Parameter, request_body::RequestBody, Array, ContentBuilder, ObjectBuilder, ResponseBuilder, Responses, ResponsesBuilder, Schema,
    SchemaType,
};
use utoipa::{openapi::RefOr, ToSchema};

/// 表格分页数据对象
pub struct HttpResPage<T: Serialize = NoData> {
    /// 状态码
    pub code: Arc<dyn IReturnCode>,
    /// 消息
    pub msg: Option<String>,
    /// 消息
    pub total: Option<u64>,
    /// 数据
    pub rows: Vec<T>,
    /// Session
    pub session: Session,
}

impl<T: Serialize> HttpResPage<T> {
    /// 构建ResPage
    pub fn success(session: &Session, total: Option<u64>, rows: Vec<T>) -> Self {
        Self {
            code: HttpStatus::Success.into_dyn_return_code(),
            msg: Some(HttpStatus::Success.get_code_description().get_string(session.get_locale())),
            total,
            rows,
            session: session.clone(),
        }
    }
    /// 从 Page 构建
    #[cfg(feature = "rbatis")]
    pub fn from_rbatis_page(session: &Session, page: ::rbatis::sql::Page<T>) -> Self {
        let total = match page.search_count {
            true => Some(page.total),
            false => None,
        };
        Self::success(session, total, page.records)
    }
    /// 构建
    #[cfg(feature = "rbatis")]
    pub fn from_rbatis_page_result(
        session: &Session,
        result: Result<::rbatis::sql::Page<T>, impl std::error::Error + Send + Sync + 'static>,
    ) -> Self {
        match result {
            Ok(d) => Self::from_rbatis_page(session, d),
            Err(err) => {
                let err = crate::app_error_from!(err);
                Self {
                    code: err.get_return_code(),
                    msg: Some(err.get_return_msg().get_string(session.get_locale())),
                    total: None,
                    rows: vec![],
                    session: session.clone(),
                }
            }
        }
    }
    /// 构建失败的ResData
    pub fn error(session: &Session, err: AppError) -> Self {
        Self {
            code: err.get_return_code(),
            msg: Some(err.get_return_msg().get_string(session.get_locale())),
            total: None,
            rows: vec![],
            session: session.clone(),
        }
    }
    /// 判断是否成功
    pub fn is_success(&self) -> bool {
        self.code.get_code() == HttpStatus::Success.get_code()
    }
}

impl<T: Serialize> Serialize for HttpResPage<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(3))?;
        map.serialize_entry("code", &self.code.get_code())?;
        map.serialize_entry("msg", &self.msg)?;
        map.serialize_entry("rows", &self.rows)?;
        map.serialize_entry("total", &self.total)?;
        map.end()
    }
}

/// 转换成 ResPage
pub trait IntoResPage<T: Serialize> {
    /// 转换成 ResPage
    fn into_page_result(self, session: &Session) -> AppResult<HttpResPage<T>>;
}

#[cfg(feature = "rbatis")]
impl<T, U> IntoResPage<U> for ::rbatis::sql::Page<T>
where
    T: TryInto<U>,
    T::Error: Into<AppError>,
    U: Serialize,
{
    fn into_page_result(self, session: &Session) -> AppResult<HttpResPage<U>> {
        let Self {
            records,
            total,
            pages: _,
            page_no: _,
            page_size: _,
            search_count,
        } = self;
        let mut vo = Vec::with_capacity(records.len());
        for item in records.into_iter() {
            vo.push(item.try_into().map_err(|err| err.into())?);
        }
        let total = match search_count {
            true => Some(total),
            false => None,
        };
        Ok(HttpResPage::success(session, total, vo))
    }
}

impl<D: Serialize + Debug> ResponseAttribute for HttpResPage<D> {
    fn success(&self) -> bool {
        self.is_success()
    }

    fn set_success(&mut self, flag: bool) {
        match flag {
            true => {
                self.code = HttpStatus::Success.into_dyn_return_code();
            }
            false => {
                self.code = HttpStatus::Error.into_dyn_return_code();
            }
        }
    }

    fn get_error_message(&self) -> Option<Cow<str>> {
        match self.is_success() {
            true => None,
            false => match self.msg.as_ref() {
                None => Some(Cow::Owned(self.code.to_string())),
                Some(s) => Some(Cow::Borrowed(s.as_str())),
            },
        }
    }
}

impl<'a, D: Serialize + Debug + ToSchema<'a>> ToSchema<'a> for HttpResPage<D> {
    fn schema() -> (&'a str, RefOr<Schema>) {
        (
            type_name::<HttpResPage<D>>(),
            RefOr::T(Schema::from(
                ObjectBuilder::new()
                    .schema_type(SchemaType::Object)
                    .description(Some("接口返回标准对象, web接口返回的标准对象, 包括对象数据、接口调用结果、异常信息等。"))
                    .property("code", Schema::from(ObjectBuilder::new().schema_type(SchemaType::Integer).description(Some("返回码"))))
                    .property("msg", Schema::from(ObjectBuilder::new().schema_type(SchemaType::String).description(Some("返回消息"))))
                    .property("total", Schema::from(ObjectBuilder::new().schema_type(SchemaType::Integer).description(Some("总记录数"))))
                    .property("rows", Schema::Array(Array::new(D::schema().1.default_description("数据")))),
            )),
        )
    }
}

impl<'a, D: Serialize + Debug + ToSchema<'a>> ApiSchema for HttpResPage<D> {
    fn get_request_body() -> Option<RequestBody>
    where
        Self: Sized,
    {
        None
    }

    fn get_request_params() -> Option<Vec<Parameter>>
    where
        Self: Sized,
    {
        None
    }

    fn get_responses() -> Responses
    where
        Self: Sized,
    {
        let (_, schema) = Self::schema();
        let description = schema.get_description();
        ResponsesBuilder::new()
            .response(
                "200",
                ResponseBuilder::new().description(description).content("application/json", ContentBuilder::new().schema(schema).build()),
            )
            .build()
    }
}