use actix_web::body::EitherBody; use actix_web::error::JsonPayloadError; use actix_web::http::StatusCode; use actix_web::{HttpRequest, HttpResponse, Responder}; use serde::{Serialize, Serializer}; use std::convert::Into; use utoipa::PartialSchema; pub struct Response(pub Result) where T: Serialize + PartialSchema, E: Serialize + PartialSchema + Clone + PartialStatusCode; pub trait PartialStatusCode { fn status_code(&self) -> StatusCode; } /// Transform Response into Result impl Into> for Response where T: Serialize + PartialSchema, E: Serialize + PartialSchema + Clone + PartialStatusCode, { fn into(self) -> Result { self.0 } } /// Transform T into Response impl From> for Response where T: Serialize + PartialSchema, E: Serialize + PartialSchema + Clone + PartialStatusCode, { fn from(value: Result) -> Self { Response(value) } } /// Serialize Response impl Serialize for Response where T: Serialize + PartialSchema, E: Serialize + PartialSchema + Clone + PartialStatusCode + Into>, { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match &self.0 { Ok(ok) => serializer.serialize_some::(&ok), Err(err) => serializer .serialize_some::>(&ResponseError::::from(err.clone().into())), } } } /// Transform Response to HttpResponse impl Responder for Response where T: Serialize + PartialSchema, E: Serialize + PartialSchema + Clone + PartialStatusCode + Into>, { type Body = EitherBody; fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_json::to_string(&self) { Ok(body) => { let code = match &self.0 { Ok(_) => StatusCode::OK, Err(e) => e.status_code(), }; match HttpResponse::build(code) .content_type(mime::APPLICATION_JSON) .message_body(body) { Ok(res) => res.map_into_left_body(), Err(err) => HttpResponse::from_error(err).map_into_right_body(), } } Err(err) => { HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body() } } } } /// ResponseError /// /// Field `message` is optional for backwards compatibility with Android App, that produces error if new fields will be added to JSON response. #[derive(Serialize, utoipa::ToSchema)] pub struct ResponseError { pub code: T, #[serde(skip_serializing_if = "Option::is_none")] pub message: Option, } pub trait IntoResponseAsError where T: Serialize + PartialSchema, Self: Serialize + PartialSchema + Clone + PartialStatusCode + Into>, { fn into_response(self) -> Response { Response(Err(self)) } } pub mod user { use crate::database::models::{User, UserRole}; use actix_macros::ResponderJson; use serde::Serialize; //noinspection SpellCheckingInspection /// Используется для скрытия чувствительных полей, таких как хеш пароля или FCM #[derive(Serialize, utoipa::ToSchema, ResponderJson)] #[serde(rename_all = "camelCase")] pub struct UserResponse { /// UUID #[schema(examples("67dcc9a9507b0000772744a2"))] id: String, /// Имя пользователя #[schema(examples("n08i40k"))] username: String, /// Группа #[schema(examples("ИС-214/23"))] group: String, /// Роль role: UserRole, /// Идентификатор привязанного аккаунта VK #[schema(examples(498094647, json!(null)))] vk_id: Option, /// JWT токен доступа #[schema(examples( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3ZGNjOWE5NTA3YjAwMDA3NzI3NDRhMiIsImlhdCI6IjE3NDMxMDgwOTkiLCJleHAiOiIxODY5MjUyMDk5In0.rMgXRb3JbT9AvLK4eiY9HMB5LxgUudkpQyoWKOypZFY" ))] access_token: String, } /// Create UserResponse from User ref. impl From<&User> for UserResponse { fn from(user: &User) -> Self { UserResponse { id: user.id.clone(), username: user.username.clone(), group: user.group.clone(), role: user.role.clone(), vk_id: user.vk_id.clone(), access_token: user.access_token.clone(), } } } /// Transform User to UserResponse. impl From for UserResponse { fn from(user: User) -> Self { UserResponse { id: user.id, username: user.username, group: user.group, role: user.role, vk_id: user.vk_id, access_token: user.access_token, } } } }