diff --git a/Cargo.toml b/Cargo.toml index 35a1639..050feae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ serde_repr = "0.1.20" sha2 = "0.11.0-pre.5" tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] } rand = "0.9.0" -utoipa = { version = "5", features = ["actix_extras"] } +utoipa = { version = "5", features = ["actix_extras", "chrono"] } utoipa-rapidoc = { version = "6.0.0", features = ["actix-web"] } utoipa-actix-web = "0.1" diff --git a/actix-macros/src/lib.rs b/actix-macros/src/lib.rs index fcc2c79..c581038 100644 --- a/actix-macros/src/lib.rs +++ b/actix-macros/src/lib.rs @@ -2,10 +2,9 @@ extern crate proc_macro; use proc_macro::TokenStream; -mod response_error_message { - use proc_macro::TokenStream; +mod shared { use quote::{ToTokens, quote}; - use syn::Attribute; + use syn::{Attribute, DeriveInput}; pub fn find_status_code(attrs: &Vec) -> Option { attrs @@ -31,7 +30,7 @@ mod response_error_message { }) } - pub fn fmt(ast: &syn::DeriveInput) -> TokenStream { + pub fn get_arms(ast: &DeriveInput) -> Vec { let name = &ast.ident; let variants = if let syn::Data::Enum(data) = &ast.data { @@ -54,21 +53,56 @@ mod response_error_message { if status_code_arms.len() < variants.len() { let status_code = find_status_code(&ast.attrs) - .unwrap_or_else(|| quote! { actix_web::http::StatusCode::INTERNAL_SERVER_ERROR }); + .unwrap_or_else(|| quote! { ::actix_web::http::StatusCode::INTERNAL_SERVER_ERROR }); status_code_arms.push(quote! { _ => #status_code }); } + status_code_arms + } +} + +mod response_error_message { + use proc_macro::TokenStream; + use quote::quote; + + pub fn fmt(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + + let status_code_arms = super::shared::get_arms(ast); + TokenStream::from(quote! { - impl actix_web::ResponseError for #name { - fn status_code(&self) -> actix_web::http::StatusCode { + impl ::actix_web::ResponseError for #name { + fn status_code(&self) -> ::actix_web::http::StatusCode { match self { #(#status_code_arms)* } } - fn error_response(&self) -> actix_web::HttpResponse { - actix_web::HttpResponse::build(self.status_code()).json(crate::utility::error::ResponseErrorMessage::new(self.clone())) + fn error_response(&self) -> ::actix_web::HttpResponse { + ::actix_web::HttpResponse::build(self.status_code()) + .json(crate::utility::error::ResponseErrorMessage::new(self.clone())) + } + } + }) + } +} + +mod status_code { + use proc_macro::TokenStream; + use quote::quote; + + pub fn fmt(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + + let status_code_arms = super::shared::get_arms(ast); + + TokenStream::from(quote! { + impl crate::routes::schema::PartialStatusCode for #name { + fn status_code(&self) -> ::actix_web::http::StatusCode { + match self { + #(#status_code_arms)* + } } } }) @@ -85,13 +119,13 @@ mod responder_json { TokenStream::from(quote! { impl ::actix_web::Responder for #name { type Body = ::actix_web::body::EitherBody<::actix_web::body::BoxBody>; - + fn respond_to(self, _: &::actix_web::HttpRequest) -> ::actix_web::HttpResponse { match ::serde_json::to_string(&self) { Ok(body) => ::actix_web::HttpResponse::Ok() .json(body) .map_into_left_body(), - + Err(err) => ::actix_web::HttpResponse::from_error( ::actix_web::error::JsonPayloadError::Serialize(err), ) @@ -103,7 +137,7 @@ mod responder_json { } } -mod into_iresponse { +mod into_response_error { use proc_macro::TokenStream; use quote::quote; @@ -111,17 +145,37 @@ mod into_iresponse { let name = &ast.ident; TokenStream::from(quote! { - impl ::core::convert::Into> for #name - where - E: ::serde::ser::Serialize - + ::utoipa::PartialSchema - + ::core::clone::Clone - + crate::routes::schema::HttpStatusCode, - { - fn into(self) -> crate::routes::schema::IResponse<#name, E> { - crate::routes::schema::IResponse(Ok(self)) + impl ::core::convert::Into> for #name { + fn into(self) -> crate::routes::schema::ResponseError<#name> { + crate::routes::schema::ResponseError { + code: self, + message: ::core::option::Option::None, + } } } + + impl crate::routes::schema::IntoResponseAsError for #name + where + T: ::serde::ser::Serialize + ::utoipa::PartialSchema {} + }) + } + + pub fn fmt_named(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + + TokenStream::from(quote! { + impl ::core::convert::Into> for #name { + fn into(self) -> crate::routes::schema::ResponseError<#name> { + crate::routes::schema::ResponseError { + message: ::core::option::Option::Some(format!("{}", self)), + code: self, + } + } + } + + impl crate::routes::schema::IntoResponseAsError for #name + where + T: ::serde::ser::Serialize + ::utoipa::PartialSchema {} }) } } @@ -140,9 +194,23 @@ pub fn responser_json_derive(input: TokenStream) -> TokenStream { responder_json::fmt(&ast) } -#[proc_macro_derive(IntoIResponse)] -pub fn into_iresponse_derive(input: TokenStream) -> TokenStream { +#[proc_macro_derive(IntoResponseError)] +pub fn into_response_error_derive(input: TokenStream) -> TokenStream { let ast = syn::parse(input).unwrap(); - into_iresponse::fmt(&ast) + into_response_error::fmt(&ast) +} + +#[proc_macro_derive(IntoResponseErrorNamed)] +pub fn into_response_error_named_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + + into_response_error::fmt_named(&ast) +} + +#[proc_macro_derive(StatusCode, attributes(status_code))] +pub fn status_code_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + + status_code::fmt(&ast) } diff --git a/src/app_state.rs b/src/app_state.rs index 3d8c432..77a49d5 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -1,11 +1,12 @@ +use crate::parser::schema::ParseResult; use crate::xls_downloader::basic_impl::BasicXlsDownloader; use actix_web::web; use chrono::{DateTime, Utc}; use diesel::{Connection, PgConnection}; use std::env; use std::sync::{Mutex, MutexGuard}; -use crate::parser::schema::ParseResult; +#[derive(Clone)] pub struct Schedule { pub etag: String, pub updated_at: DateTime, diff --git a/src/main.rs b/src/main.rs index acfc11e..13cf9c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use actix_web::{App, HttpServer}; use dotenvy::dotenv; use utoipa_actix_web::AppExt; use utoipa_rapidoc::RapiDoc; +use crate::routes::schedule::get_schedule::get_schedule; mod app_state; @@ -40,10 +41,15 @@ async fn main() { let users_scope = utoipa_actix_web::scope("/users") .wrap(Authorization) .service(me); + + let schedule_scope = utoipa_actix_web::scope("/schedule") + .wrap(Authorization) + .service(get_schedule); let api_scope = utoipa_actix_web::scope("/api/v1") .service(auth_scope) - .service(users_scope); + .service(users_scope) + .service(schedule_scope); let (app, api) = App::new() .into_utoipa_app() diff --git a/src/parser/schema.rs b/src/parser/schema.rs index 4c8f226..09aae4a 100644 --- a/src/parser/schema.rs +++ b/src/parser/schema.rs @@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::collections::HashMap; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, utoipa::ToSchema)] pub struct LessonTime { pub start: DateTime, pub end: DateTime, } -#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone)] +#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, utoipa::ToSchema)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[repr(u8)] pub enum LessonType { Default = 0, // Обычная @@ -22,7 +23,7 @@ pub enum LessonType { ExamDefault, // Экзамен } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive( Serialize, Deserialize, Debug, Clone, utoipa::ToSchema)] pub struct LessonSubGroup { /** * Номер подгруппы. @@ -40,7 +41,8 @@ pub struct LessonSubGroup { pub teacher: String, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, utoipa::ToSchema)] +#[serde(rename_all = "camelCase")] pub struct Lesson { /** * Тип занятия. @@ -51,7 +53,6 @@ pub struct Lesson { /** * Индексы пар, если присутствуют. */ - #[serde(rename = "defaultRange")] pub default_range: Option<[u8; 2]>, /** @@ -76,7 +77,7 @@ pub struct Lesson { pub group: Option, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, utoipa::ToSchema)] pub struct Day { /** * День недели. @@ -99,7 +100,7 @@ pub struct Day { pub lessons: Vec, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug, utoipa::ToSchema)] pub struct ScheduleEntry { /** * Название группы или ФИО преподавателя. @@ -112,6 +113,7 @@ pub struct ScheduleEntry { pub days: Vec, } +#[derive(Clone)] pub struct ParseResult { /** * Список групп. diff --git a/src/routes/auth/sign_in.rs b/src/routes/auth/sign_in.rs index 95812e0..6d698b6 100644 --- a/src/routes/auth/sign_in.rs +++ b/src/routes/auth/sign_in.rs @@ -4,14 +4,17 @@ use crate::database::models::User; use crate::routes::auth::shared::parse_vk_id; use crate::routes::auth::sign_in::schema::SignInData::{Default, Vk}; use crate::routes::schema::user::UserResponse; -use crate::routes::schema::ResponseError; -use crate::{utility, AppState}; +use crate::routes::schema::{IntoResponseAsError, ResponseError}; +use crate::{AppState, utility}; use actix_web::{post, web}; use diesel::SaveChangesDsl; use std::ops::DerefMut; use web::Json; -async fn sign_in(data: SignInData, app_state: &web::Data) -> Response { +async fn sign_in( + data: SignInData, + app_state: &web::Data, +) -> Result { let user = match &data { Default(data) => driver::users::get_by_username(&app_state.database, &data.username), Vk(id) => driver::users::get_by_vk_id(&app_state.database, *id), @@ -23,11 +26,11 @@ async fn sign_in(data: SignInData, app_state: &web::Data) -> Response match bcrypt::verify(&data.password, &user.password) { Ok(result) => { if !result { - return ErrorCode::IncorrectCredentials.into(); + return Err(ErrorCode::IncorrectCredentials); } } Err(_) => { - return ErrorCode::IncorrectCredentials.into(); + return Err(ErrorCode::IncorrectCredentials); } } } @@ -40,10 +43,10 @@ async fn sign_in(data: SignInData, app_state: &web::Data) -> Response user.save_changes::(conn) .expect("Failed to update user"); - UserResponse::from(&user).into() + Ok(user.into()) } - Err(_) => ErrorCode::IncorrectCredentials.into(), + Err(_) => Err(ErrorCode::IncorrectCredentials), } } @@ -53,7 +56,7 @@ async fn sign_in(data: SignInData, app_state: &web::Data) -> Response ))] #[post("/sign-in")] pub async fn sign_in_default(data: Json, app_state: web::Data) -> Response { - sign_in(Default(data.into_inner()), &app_state).await + sign_in(Default(data.into_inner()), &app_state).await.into() } #[utoipa::path(responses( @@ -65,14 +68,15 @@ pub async fn sign_in_vk(data_json: Json, app_state: web::Data sign_in(Vk(id), &app_state).await, - Err(_) => ErrorCode::InvalidVkAccessToken.into(), + Ok(id) => sign_in(Vk(id), &app_state).await.into(), + Err(_) => ErrorCode::InvalidVkAccessToken.into_response(), } } mod schema { + use crate::routes::schema::PartialStatusCode; use crate::routes::schema::user::UserResponse; - use crate::routes::schema::{HttpStatusCode, IResponse}; + use actix_macros::IntoResponseError; use actix_web::http::StatusCode; use serde::{Deserialize, Serialize}; @@ -95,9 +99,9 @@ mod schema { } } - pub type Response = IResponse; + pub type Response = crate::routes::schema::Response; - #[derive(Serialize, utoipa::ToSchema, Clone)] + #[derive(Serialize, utoipa::ToSchema, Clone, IntoResponseError)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[schema(as = SignIn::ErrorCode)] pub enum ErrorCode { @@ -105,7 +109,7 @@ mod schema { InvalidVkAccessToken, } - impl HttpStatusCode for ErrorCode { + impl PartialStatusCode for ErrorCode { fn status_code(&self) -> StatusCode { StatusCode::NOT_ACCEPTABLE } diff --git a/src/routes/auth/sign_up.rs b/src/routes/auth/sign_up.rs index 2c4baaf..db53dc8 100644 --- a/src/routes/auth/sign_up.rs +++ b/src/routes/auth/sign_up.rs @@ -3,16 +3,19 @@ use crate::AppState; use crate::database::driver; use crate::database::models::UserRole; use crate::routes::auth::shared::{Error, parse_vk_id}; -use crate::routes::schema::ResponseError; use crate::routes::schema::user::UserResponse; +use crate::routes::schema::{IntoResponseAsError, ResponseError}; use actix_web::{post, web}; use rand::{Rng, rng}; use web::Json; -async fn sign_up(data: SignUpData, app_state: &web::Data) -> Response { +async fn sign_up( + data: SignUpData, + app_state: &web::Data, +) -> Result { // If user selected forbidden role. if data.role == UserRole::Admin { - return ErrorCode::DisallowedRole.into(); + return Err(ErrorCode::DisallowedRole); } // If specified group doesn't exist in schedule. @@ -20,26 +23,26 @@ async fn sign_up(data: SignUpData, app_state: &web::Data) -> Response if let Some(schedule) = &*schedule_opt { if !schedule.data.groups.contains_key(&data.group) { - return ErrorCode::InvalidGroupName.into(); + return Err(ErrorCode::InvalidGroupName); } } // If user with specified username already exists. if driver::users::contains_by_username(&app_state.database, &data.username) { - return ErrorCode::UsernameAlreadyExists.into(); + return Err(ErrorCode::UsernameAlreadyExists); } // If user with specified VKID already exists. if let Some(id) = data.vk_id { if driver::users::contains_by_vk_id(&app_state.database, id) { - return ErrorCode::VkAlreadyExists.into(); + return Err(ErrorCode::VkAlreadyExists); } } let user = data.into(); driver::users::insert(&app_state.database, &user).unwrap(); - UserResponse::from(&user).into() + Ok(UserResponse::from(&user)).into() } #[utoipa::path(responses( @@ -62,6 +65,7 @@ pub async fn sign_up_default(data_json: Json, app_state: web::Data, app_state: web::Data) ))] #[post("/sign-up-vk")] -pub async fn sign_up_vk( - data_json: Json, - app_state: web::Data, -) -> Response { +pub async fn sign_up_vk(data_json: Json, app_state: web::Data) -> Response { let data = data_json.into_inner(); match parse_vk_id(&data.access_token) { - Ok(id) => { - sign_up( - SignUpData { - username: data.username, - password: rng() - .sample_iter(&rand::distr::Alphanumeric) - .take(16) - .map(char::from) - .collect(), - vk_id: Some(id), - group: data.group, - role: data.role, - version: data.version, - }, - &app_state, - ) - .await - } + Ok(id) => sign_up( + SignUpData { + username: data.username, + password: rng() + .sample_iter(&rand::distr::Alphanumeric) + .take(16) + .map(char::from) + .collect(), + vk_id: Some(id), + group: data.group, + role: data.role, + version: data.version, + }, + &app_state, + ) + .await + .into(), Err(err) => { if err != Error::Expired { eprintln!("Failed to parse vk id token!"); eprintln!("{:?}", err); } - ErrorCode::InvalidVkAccessToken.into() + ErrorCode::InvalidVkAccessToken.into_response() } } } mod schema { use crate::database::models::{User, UserRole}; + use crate::routes::schema::PartialStatusCode; use crate::routes::schema::user::UserResponse; - use crate::routes::schema::{HttpStatusCode, IResponse}; use crate::utility; + use actix_macros::{IntoResponseError, StatusCode}; use actix_web::http::StatusCode; use objectid::ObjectId; use serde::{Deserialize, Serialize}; @@ -156,9 +157,10 @@ mod schema { } } - pub type Response = IResponse; + pub type Response = crate::routes::schema::Response; - #[derive(Clone, Serialize, utoipa::ToSchema)] + #[derive(Clone, Serialize, utoipa::ToSchema, IntoResponseError, StatusCode)] + #[status_code = "StatusCode::NOT_ACCEPTABLE"] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[schema(as = SignUp::ErrorCode)] pub enum ErrorCode { @@ -169,12 +171,6 @@ mod schema { VkAlreadyExists, } - impl HttpStatusCode for ErrorCode { - fn status_code(&self) -> StatusCode { - StatusCode::NOT_ACCEPTABLE - } - } - /// Internal pub struct SignUpData { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 1d8bb45..9fae8c0 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ pub mod auth; pub mod users; +pub mod schedule; mod schema; diff --git a/src/routes/schedule/get_schedule.rs b/src/routes/schedule/get_schedule.rs new file mode 100644 index 0000000..2283c42 --- /dev/null +++ b/src/routes/schedule/get_schedule.rs @@ -0,0 +1,38 @@ +use self::schema::*; +use crate::app_state::AppState; +use crate::routes::schedule::schema::{Error, ScheduleView}; +use crate::routes::schema::{IntoResponseAsError, ResponseError}; +use actix_web::{get, web}; + +#[utoipa::path(responses( + (status = OK, body = ScheduleView), + (status = SERVICE_UNAVAILABLE, body = ResponseError) +))] +#[get("/")] +pub async fn get_schedule(app_state: web::Data) -> Response { + match ScheduleView::try_from(app_state.get_ref()) { + Ok(res) => Ok(res).into(), + Err(e) => match e { + Error::NoSchedule => ErrorCode::NoSchedule.into_response(), + }, + } +} + +mod schema { + use crate::routes::schedule::schema::ScheduleView; + use actix_macros::{IntoResponseErrorNamed, StatusCode}; + use derive_more::Display; + use serde::Serialize; + use utoipa::ToSchema; + + pub type Response = crate::routes::schema::Response; + + #[derive(Clone, Serialize, ToSchema, StatusCode, Display, IntoResponseErrorNamed)] + #[status_code = "actix_web::http::StatusCode::SERVICE_UNAVAILABLE"] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + #[schema(as = ScheduleView::ErrorCode)] + pub enum ErrorCode { + #[display("Schedule not parsed yet")] + NoSchedule, + } +} diff --git a/src/routes/schedule/mod.rs b/src/routes/schedule/mod.rs new file mode 100644 index 0000000..1536e95 --- /dev/null +++ b/src/routes/schedule/mod.rs @@ -0,0 +1,2 @@ +pub mod get_schedule; +mod schema; \ No newline at end of file diff --git a/src/routes/schedule/schema.rs b/src/routes/schedule/schema.rs new file mode 100644 index 0000000..6f34e84 --- /dev/null +++ b/src/routes/schedule/schema.rs @@ -0,0 +1,47 @@ +use crate::app_state::AppState; +use crate::parser::schema::ScheduleEntry; +use chrono::{DateTime, Utc}; +use serde::Serialize; +use std::collections::HashMap; +use utoipa::ToSchema; + +#[derive(Serialize, ToSchema)] +pub struct ScheduleView { + etag: String, + replacer_id: Option, + uploaded_at: DateTime, + downloaded_at: DateTime, + groups: HashMap, + teachers: HashMap, + updated_groups: Vec>, + updated_teachers: Vec>, +} + +pub enum Error { + NoSchedule, +} + +impl TryFrom<&AppState> for ScheduleView { + type Error = Error; + + fn try_from(app_state: &AppState) -> Result { + let schedule_lock = app_state.schedule.lock().unwrap(); + + if let Some(schedule_ref) = schedule_lock.as_ref() { + let schedule = schedule_ref.clone(); + + Ok(Self { + etag: schedule.etag, + replacer_id: None, + uploaded_at: schedule.updated_at, + downloaded_at: schedule.parsed_at, + groups: schedule.data.groups, + teachers: schedule.data.teachers, + updated_groups: vec![], + updated_teachers: vec![], + }) + } else { + Err(Error::NoSchedule) + } + } +} diff --git a/src/routes/schema.rs b/src/routes/schema.rs index 74124e3..3a0bce9 100644 --- a/src/routes/schema.rs +++ b/src/routes/schema.rs @@ -3,51 +3,45 @@ 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 IResponse(pub Result) +pub struct Response(pub Result) where T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode; + E: Serialize + PartialSchema + Clone + PartialStatusCode; -impl Into> for IResponse +pub trait PartialStatusCode { + fn status_code(&self) -> StatusCode; +} + +/// Transform Response into Result +impl Into> for Response where T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode, + E: Serialize + PartialSchema + Clone + PartialStatusCode, { fn into(self) -> Result { self.0 } } -impl From for IResponse +/// Transform T into Response +impl From> for Response where T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode, + E: Serialize + PartialSchema + Clone + PartialStatusCode, { - fn from(value: E) -> Self { - IResponse(Err(value)) + fn from(value: Result) -> Self { + Response(value) } } -pub trait HttpStatusCode { - fn status_code(&self) -> StatusCode; -} - -impl IResponse +/// Serialize Response +impl Serialize for Response where T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode, -{ - pub fn new(result: Result) -> Self { - IResponse(result) - } -} - -impl Serialize for IResponse -where - T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode, + E: Serialize + PartialSchema + Clone + PartialStatusCode + Into>, { fn serialize(&self, serializer: S) -> Result where @@ -55,15 +49,17 @@ where { match &self.0 { Ok(ok) => serializer.serialize_some::(&ok), - Err(err) => serializer.serialize_some::>(&ResponseError::new(err)), + Err(err) => serializer + .serialize_some::>(&ResponseError::::from(err.clone().into())), } } } -impl Responder for IResponse +/// Transform Response to HttpResponse +impl Responder for Response where T: Serialize + PartialSchema, - E: Serialize + PartialSchema + Clone + HttpStatusCode, + E: Serialize + PartialSchema + Clone + PartialStatusCode + Into>, { type Body = EitherBody; @@ -91,25 +87,36 @@ where } } +/// 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 { - code: T, + pub code: T, + + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, } -impl ResponseError { - fn new(status_code: &T) -> Self { - ResponseError { - code: status_code.clone(), - } +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::{IntoIResponse, ResponderJson}; + use actix_macros::ResponderJson; use serde::Serialize; - #[derive(Serialize, utoipa::ToSchema, IntoIResponse, ResponderJson)] + /// UserResponse + /// + /// Uses for stripping sensitive fields (password, fcm, etc.) from response. + #[derive(Serialize, utoipa::ToSchema, ResponderJson)] #[serde(rename_all = "camelCase")] pub struct UserResponse { #[schema(examples("67dcc9a9507b0000772744a2"))] @@ -132,6 +139,7 @@ pub mod user { access_token: String, } + /// Create UserResponse from User ref. impl From<&User> for UserResponse { fn from(user: &User) -> Self { UserResponse { @@ -145,6 +153,7 @@ pub mod user { } } + /// Transform User to UserResponse. impl From for UserResponse { fn from(user: User) -> Self { UserResponse {