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>,
pub session: Session,
}
impl<T: Serialize, Ext: Serialize> HttpResData<T, Ext> {
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(),
}
}
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(),
}
}
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(),
}
}
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(),
}
}
}
}
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()
}
}
pub trait ToResData<D: Serialize, Ext: Serialize> {
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()
}
}