tina-core 0.0.2

Tina platform
Documentation
//! 操作消息提醒

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

#[derive(Debug)]
/// 操作消息提醒
pub struct HttpResData<T: Serialize = NoData, Ext: Serialize = NoData> {
    /// 状态码
    pub code: Arc<dyn IReturnCode>,
    /// 消息
    pub msg: Option<String>,
    /// 数据
    pub data: Option<T>,
    /// 扩展
    pub extension: Option<Ext>,
    /// Session
    pub session: Session,
}

impl<T: Serialize, Ext: Serialize> HttpResData<T, Ext> {
    /// 构建ResData
    pub fn success(session: &Session, data: Option<T>) -> Self {
        Self {
            code: HttpStatus::Success.into_dyn_return_code(),
            msg: Some(HttpStatus::Success.get_code_description().get_string(session.get_locale())),
            data,
            extension: None,
            session: session.clone(),
        }
    }
    /// 构建ResData
    pub fn success_with_ext(session: &Session, data: Option<T>, extension: Option<Ext>) -> Self {
        Self {
            code: HttpStatus::Success.into_dyn_return_code(),
            msg: Some(HttpStatus::Success.get_code_description().get_string(session.get_locale())),
            data,
            extension,
            session: session.clone(),
        }
    }
    /// 构建ResData
    pub fn success_with_msg(session: &Session, data: Option<T>, msg: I18nString) -> Self {
        Self {
            code: HttpStatus::Success.into_dyn_return_code(),
            msg: Some(msg.get_string(session.get_locale())),
            data,
            extension: None,
            session: session.clone(),
        }
    }
    /// 构建ResData
    pub fn success_with_ext_msg(session: &Session, data: Option<T>, extension: Option<Ext>, msg: I18nString) -> Self {
        Self {
            code: HttpStatus::Success.into_dyn_return_code(),
            msg: Some(msg.get_string(session.get_locale())),
            data,
            extension,
            session: session.clone(),
        }
    }
    /// 构建
    pub fn new(session: &Session, result: Result<T, impl std::error::Error + Send + Sync + 'static>) -> Self {
        match result {
            Ok(d) => Self {
                code: HttpStatus::Success.into_dyn_return_code(),
                msg: Some(HttpStatus::Success.get_code_description().get_string(session.get_locale())),
                data: Some(d),
                extension: None,
                session: session.clone(),
            },
            Err(err) => {
                let err = app_error_from!(err);
                Self {
                    code: err.get_return_code(),
                    msg: Some(err.get_return_msg().get_string(session.get_locale())),
                    data: None,
                    extension: None,
                    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())),
            data: None,
            extension: Default::default(),
            session: session.clone(),
        }
    }
    /// 判断是否成功
    pub fn is_success(&self) -> bool {
        self.code.get_code() == HttpStatus::Success.get_code()
    }
}

impl<T: Serialize, Ext: Serialize> Serialize for HttpResData<T, Ext> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let extension = self.extension.to_json_value();
        let ext_len = match &extension {
            Value::Null => 0,
            Value::Bool(_) => 1,
            Value::Number(_) => 1,
            Value::String(_) => 1,
            Value::Array(_) => 1,
            Value::Object(v) => v.len(),
        };
        let data_len = match self.data.as_ref() {
            None => 0,
            Some(_) => 1,
        };
        let mut map = serializer.serialize_map(Some(2 + data_len + ext_len))?;
        map.serialize_entry("code", &self.code.get_code())?;
        map.serialize_entry("msg", &self.msg)?;
        if let Some(data) = self.data.as_ref() {
            map.serialize_entry("data", data)?;
        }
        match extension {
            Value::Null => {}
            Value::Bool(v) => {
                map.serialize_entry("extension", &v)?;
            }
            Value::Number(v) => {
                map.serialize_entry("extension", &v)?;
            }
            Value::String(v) => {
                map.serialize_entry("extension", &v)?;
            }
            Value::Array(v) => {
                map.serialize_entry("extension", &v)?;
            }
            Value::Object(obj) => {
                for (k, v) in obj.iter() {
                    map.serialize_entry(k, v)?;
                }
            }
        }
        map.end()
    }
}

/// 转换为ResData
pub trait ToResData<D: Serialize, Ext: Serialize> {
    /// 转换为ResData
    fn to_response_data(&self, session: &Session) -> HttpResData<D, Ext>;
}

impl ToResData<NoData, NoData> for bool {
    fn to_response_data(&self, session: &Session) -> HttpResData<NoData, NoData> {
        match *self {
            true => HttpResData::success(session, None),
            false => HttpResData::error(session, app_system_error_with_msg!(i18n_string!(SystemMessage::ERROR_FAILED))),
        }
    }
}

macro_rules! impl_default_to_response_data {
    ($ty:ty) => {
        impl ToResData<NoData, NoData> for $ty {
            fn to_response_data(&self, session: &Session) -> HttpResData<NoData, NoData> {
                match *self as f64 > 0.0 {
                    true => HttpResData::success(session, None),
                    false => HttpResData::error(session, app_system_error_with_msg!(i18n_string!(SystemMessage::ERROR_FAILED))),
                }
            }
        }
    };
}

impl_default_to_response_data!(i8);
impl_default_to_response_data!(i16);
impl_default_to_response_data!(i32);
impl_default_to_response_data!(i64);
impl_default_to_response_data!(u8);
impl_default_to_response_data!(u16);
impl_default_to_response_data!(u32);
impl_default_to_response_data!(u64);
impl_default_to_response_data!(f32);
impl_default_to_response_data!(f64);

impl<D: Serialize + Debug, Ext: Serialize + Debug> ResponseAttribute for HttpResData<D, Ext> {
    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>, Ext: Serialize + Debug + ToSchema<'a>> ToSchema<'a> for HttpResData<D, Ext> {
    fn schema() -> (&'a str, RefOr<Schema>) {
        let ext_props = Ext::schema().1.get_schema_props();
        let mut builder = 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("data", D::schema().1.default_description("数据"));
        for (k, v) in ext_props.into_iter() {
            builder = builder.property(k, v);
        }
        (type_name::<HttpResData<D, Ext>>(), RefOr::T(Schema::from(builder)))
    }
}

impl<'a, D: Serialize + Debug + ToSchema<'a>, Ext: Serialize + Debug + ToSchema<'a>> ApiSchema for HttpResData<D, Ext> {
    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()
    }
}