Возвращёна реализация сериализации в json для IResponse

Добавлены типы для экстракции данных из запросов средствами actix-web

Добавлен экстрактор для получения пользователя по токену доступа передаваемому в запросе

Добавлен макрос для автоматической реализации ResponseError для ошибок экстракторов

Добавлен эндпоинт users/me

Из главного проекта исключена зависимость actix-http посредством переноса части тестового функционала в отдельный crate
This commit is contained in:
2025-03-26 08:05:22 +04:00
parent ab1cbd795e
commit f703cc8326
24 changed files with 2022 additions and 34 deletions

View File

@@ -5,6 +5,10 @@
<sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/actix-macros/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/actix-test/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/actix-macros/target" />
<excludeFolder url="file://$MODULE_DIR$/actix-test/target" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

164
Cargo.lock generated
View File

@@ -58,6 +58,15 @@ dependencies = [
"zstd", "zstd",
] ]
[[package]]
name = "actix-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "actix-macros" name = "actix-macros"
version = "0.2.4" version = "0.2.4"
@@ -120,6 +129,14 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "actix-test"
version = "0.1.0"
dependencies = [
"actix-http",
"actix-web",
]
[[package]] [[package]]
name = "actix-utils" name = "actix-utils"
version = "3.0.1" version = "3.0.1"
@@ -138,7 +155,7 @@ checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d"
dependencies = [ dependencies = [
"actix-codec", "actix-codec",
"actix-http", "actix-http",
"actix-macros", "actix-macros 0.2.4",
"actix-router", "actix-router",
"actix-rt", "actix-rt",
"actix-server", "actix-server",
@@ -245,12 +262,56 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
version = "1.4.1" version = "1.4.1"
@@ -518,6 +579,12 @@ dependencies = [
"encoding_rs", "encoding_rs",
] ]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.10.0" version = "0.10.0"
@@ -845,6 +912,29 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -934,6 +1024,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@@ -953,9 +1054,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-macro",
"futures-task", "futures-task",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab",
] ]
[[package]] [[package]]
@@ -1462,6 +1565,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@@ -1477,6 +1586,30 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.32" version = "0.1.32"
@@ -1830,6 +1963,21 @@ dependencies = [
"plotters-backend", "plotters-backend",
] ]
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -2203,17 +2351,21 @@ dependencies = [
[[package]] [[package]]
name = "schedule-parser-rusted" name = "schedule-parser-rusted"
version = "0.4.0" version = "0.5.0"
dependencies = [ dependencies = [
"actix-http", "actix-macros 0.1.0",
"actix-test",
"actix-web", "actix-web",
"bcrypt", "bcrypt",
"calamine", "calamine",
"chrono", "chrono",
"criterion", "criterion",
"derive_more",
"diesel", "diesel",
"diesel-derive-enum", "diesel-derive-enum",
"dotenvy", "dotenvy",
"env_logger",
"futures-util",
"fuzzy-matcher", "fuzzy-matcher",
"jsonwebtoken", "jsonwebtoken",
"mime", "mime",
@@ -2772,6 +2924,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@@ -1,17 +1,24 @@
[workspace]
members = ["actix-macros", "actix-test"]
[package] [package]
name = "schedule-parser-rusted" name = "schedule-parser-rusted"
version = "0.4.0" version = "0.5.0"
edition = "2024" edition = "2024"
publish = false publish = false
[dependencies] [dependencies]
actix-web = "4.10.2" actix-web = "4.10.2"
actix-macros = { path = "actix-macros" }
bcrypt = "0.17.0" bcrypt = "0.17.0"
calamine = "0.26.1" calamine = "0.26.1"
chrono = { version = "0.4.40", features = ["serde"] } chrono = { version = "0.4.40", features = ["serde"] }
derive_more = "2.0.1"
diesel = { version = "2.2.8", features = ["postgres"] } diesel = { version = "2.2.8", features = ["postgres"] }
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] } diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
dotenvy = "0.15.7" dotenvy = "0.15.7"
env_logger = "0.11.7"
futures-util = "0.3.31"
fuzzy-matcher = "0.3.7" fuzzy-matcher = "0.3.7"
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] } jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
mime = "0.3.17" mime = "0.3.17"
@@ -27,9 +34,9 @@ tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
rand = "0.9.0" rand = "0.9.0"
[dev-dependencies] [dev-dependencies]
actix-http = "3.10.0" actix-test = { path = "actix-test" }
criterion = "0.5.1" criterion = "0.5.1"
[[bench]] [[bench]]
name = "parse" name = "parse"
harness = false harness = false

1
actix-macros/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

7
actix-macros/Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "actix-utility-macros"
version = "0.1.0"

12
actix-macros/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "actix-macros"
version = "0.1.0"
edition = "2024"
[dependencies]
syn = "2.0.100"
quote = "1.0.40"
proc-macro2 = "1.0.94"
[lib]
proc-macro = true

79
actix-macros/src/lib.rs Normal file
View File

@@ -0,0 +1,79 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::Attribute;
fn find_status_code(attrs: &Vec<Attribute>) -> Option<proc_macro2::TokenStream> {
attrs
.iter()
.find_map(|attr| -> Option<proc_macro2::TokenStream> {
if !attr.path().is_ident("status_code") {
return None;
}
let meta = attr.meta.require_name_value().ok()?;
let code = meta.value.to_token_stream().to_string();
let trimmed_code = code.trim_matches('"');
if let Ok(numeric_code) = trimmed_code.parse::<u16>() {
Some(quote! { actix_web::http::StatusCode::from_u16(#numeric_code).unwrap() })
} else {
let string_code: proc_macro2::TokenStream =
trimmed_code.to_string().parse().unwrap();
Some(quote! { #string_code })
}
})
}
fn impl_rem(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let variants = if let syn::Data::Enum(data) = &ast.data {
&data.variants
} else {
panic!("Only enums are supported");
};
let mut status_code_arms: Vec<proc_macro2::TokenStream> = variants
.iter()
.map(|v| -> Option<proc_macro2::TokenStream> {
let status_code = find_status_code(&v.attrs)?;
let variant_name = &v.ident;
Some(quote! { #name::#variant_name => #status_code, })
})
.filter(|v| v.is_some())
.map(|v| v.unwrap())
.collect();
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 });
status_code_arms.push(quote! { _ => #status_code });
}
TokenStream::from(quote! {
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<BoxBody> {
actix_web::HttpResponse::build(self.status_code()).json(crate::utility::error::ResponseErrorMessage::new(self.clone()))
}
}
})
}
#[proc_macro_derive(ResponseErrorMessage, attributes(status_code))]
pub fn rem_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_rem(&ast)
}

1
actix-test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1520
actix-test/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

8
actix-test/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "actix-test"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-http = "3.10.0"
actix-web = "4.10.2"

12
actix-test/src/lib.rs Normal file
View File

@@ -0,0 +1,12 @@
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
use actix_web::{App, test, web};
pub async fn test_app<F, A: 'static>(
app_state: web::Data<A>,
factory: F,
) -> impl Service<actix_http::Request, Response = ServiceResponse, Error = actix_web::Error>
where
F: HttpServiceFactory + 'static,
{
test::init_service(App::new().app_data(app_state).service(factory)).await
}

View File

@@ -0,0 +1,69 @@
use crate::app_state::AppState;
use crate::database::driver;
use crate::database::models::User;
use crate::extractors::base::FromRequestSync;
use crate::utility::jwt;
use actix_macros::ResponseErrorMessage;
use actix_web::body::BoxBody;
use actix_web::dev::Payload;
use actix_web::{HttpRequest, web};
use derive_more::Display;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Clone, Debug, Serialize, Deserialize, Display, ResponseErrorMessage)]
#[status_code = "actix_web::http::StatusCode::UNAUTHORIZED"]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Error {
#[display("No authorization header found")]
NoHeader,
#[display("Bearer token is required")]
UnknownAuthorizationType,
#[display("No bearer token provided")]
NoBearerToken,
#[display("Invalid or expired access token")]
InvalidAccessToken,
#[display("No user associated with access token")]
NoUser,
}
impl Error {
pub fn into_err(self) -> actix_web::Error {
actix_web::Error::from(self)
}
}
impl FromRequestSync for User {
type Error = actix_web::Error;
fn from_request_sync(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
let authorization = req
.headers()
.get("Authorization")
.ok_or(Error::NoHeader.into_err())?
.to_str()
.map_err(|_| Error::NoHeader.into_err())?
.to_string();
let parts: Vec<&str> = authorization.split(' ').collect();
if parts.len() == 0 || parts[0] != "Bearer" {
return Err(Error::UnknownAuthorizationType.into_err());
}
if parts.len() < 2 {
return Err(Error::NoBearerToken.into());
}
let user_id = jwt::verify_and_decode(&parts[1].to_string())
.map_err(|_| Error::InvalidAccessToken.into_err())?;
let app_state = req.app_data::<web::Data<AppState>>().unwrap();
driver::users::get(&app_state.database, &user_id).map_err(|_| Error::NoUser.into())
}
}

56
src/extractors/base.rs Normal file
View File

@@ -0,0 +1,56 @@
use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest};
use futures_util::future::LocalBoxFuture;
use std::future::{Ready, ready};
pub trait FromRequestAsync: Sized {
type Error: Into<actix_web::Error>;
async fn from_request_async(req: HttpRequest, payload: Payload) -> Result<Self, Self::Error>;
}
pub struct AsyncExtractor<T>(T);
impl<T> AsyncExtractor<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: FromRequestAsync> FromRequest for AsyncExtractor<T> {
type Error = T::Error;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req = req.clone();
let payload = payload.take();
Box::pin(async move {
T::from_request_async(req, payload)
.await
.map(|res| Self(res))
})
}
}
pub trait FromRequestSync: Sized {
type Error: Into<actix_web::Error>;
fn from_request_sync(req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error>;
}
pub struct SyncExtractor<T>(T);
impl<T> SyncExtractor<T> {
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: FromRequestSync> FromRequest for SyncExtractor<T> {
type Error = T::Error;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
ready(T::from_request_sync(req, payload).map(|res| Self(res)))
}
}

2
src/extractors/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod authorized_user;
pub mod base;

View File

@@ -1,33 +1,44 @@
use crate::app_state::{AppState, app_state}; use crate::app_state::{AppState, app_state};
use crate::routes::auth::sign_in::{sign_in_default, sign_in_vk}; use crate::routes::auth::sign_in::{sign_in_default, sign_in_vk};
use crate::routes::auth::sign_up::{sign_up_default, sign_up_vk}; use crate::routes::auth::sign_up::{sign_up_default, sign_up_vk};
use crate::routes::users::me::me;
use actix_web::{App, HttpServer, web}; use actix_web::{App, HttpServer, web};
use dotenvy::dotenv; use dotenvy::dotenv;
mod app_state; mod app_state;
mod database; mod database;
mod routes;
mod test_env;
mod utility;
mod xls_downloader;
mod parser; mod parser;
mod xls_downloader;
mod extractors;
mod routes;
mod utility;
mod test_env;
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
dotenv().ok(); dotenv().ok();
unsafe { std::env::set_var("RUST_LOG", "debug") };
env_logger::init();
HttpServer::new(move || { HttpServer::new(move || {
let auth_scope = web::scope("/auth") let auth_scope = web::scope("/auth")
.service(sign_in_default) .service(sign_in_default)
.service(sign_in_vk) .service(sign_in_vk)
.service(sign_up_default) .service(sign_up_default)
.service(sign_up_vk); .service(sign_up_vk);
let api_scope = web::scope("/api/v1").service(auth_scope); let users_scope = web::scope("/users").service(me);
App::new().app_data(move || app_state()).service(api_scope) let api_scope = web::scope("/api/v1")
.service(auth_scope)
.service(users_scope);
App::new().app_data(app_state()).service(api_scope)
}) })
.bind(("127.0.0.1", 8080)) .bind(("127.0.0.1", 8080))
.unwrap() .unwrap()

View File

@@ -132,14 +132,15 @@ mod tests {
use crate::database::driver; use crate::database::driver;
use crate::database::models::{User, UserRole}; use crate::database::models::{User, UserRole};
use crate::routes::auth::sign_in::sign_in_default; use crate::routes::auth::sign_in::sign_in_default;
use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env}; use crate::test_env::tests::{static_app_state, test_app_state, test_env};
use crate::utility; use crate::utility;
use actix_http::StatusCode; use actix_web::http::StatusCode;
use actix_web::dev::ServiceResponse; use actix_web::dev::ServiceResponse;
use actix_web::http::Method; use actix_web::http::Method;
use actix_web::test; use actix_web::test;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fmt::Write; use std::fmt::Write;
use actix_test::test_app;
async fn sign_in_client(data: Request) -> ServiceResponse { async fn sign_in_client(data: Request) -> ServiceResponse {
let app = test_app(test_app_state(), sign_in_default).await; let app = test_app(test_app_state(), sign_in_default).await;

View File

@@ -208,11 +208,12 @@ mod tests {
use crate::database::models::UserRole; use crate::database::models::UserRole;
use crate::routes::auth::sign_up::schema::Request; use crate::routes::auth::sign_up::schema::Request;
use crate::routes::auth::sign_up::sign_up_default; use crate::routes::auth::sign_up::sign_up_default;
use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env}; use crate::test_env::tests::{static_app_state, test_app_state, test_env};
use actix_http::StatusCode; use actix_web::http::StatusCode;
use actix_web::dev::ServiceResponse; use actix_web::dev::ServiceResponse;
use actix_web::http::Method; use actix_web::http::Method;
use actix_web::test; use actix_web::test;
use actix_test::test_app;
struct SignUpPartial { struct SignUpPartial {
username: String, username: String,

View File

@@ -1,2 +1,3 @@
pub mod auth; pub mod auth;
pub mod users;
mod schema; mod schema;

View File

@@ -2,7 +2,7 @@ use actix_web::body::EitherBody;
use actix_web::error::JsonPayloadError; use actix_web::error::JsonPayloadError;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::{HttpRequest, HttpResponse, Responder}; use actix_web::{HttpRequest, HttpResponse, Responder};
use serde::Serialize; use serde::{Serialize, Serializer};
pub struct IResponse<T: Serialize, E: Serialize>(pub Result<T, E>); pub struct IResponse<T: Serialize, E: Serialize>(pub Result<T, E>);
@@ -10,11 +10,29 @@ pub trait ErrorToHttpCode {
fn to_http_status_code(&self) -> StatusCode; fn to_http_status_code(&self) -> StatusCode;
} }
impl<T: Serialize, E: Serialize> IResponse<T, E> {
pub fn new(result: Result<T, E>) -> Self {
IResponse(result)
}
}
impl<T: Serialize, E: Serialize> Serialize for IResponse<T, E> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.0 {
Ok(ok) => serializer.serialize_some::<T>(&ok),
Err(err) => serializer.serialize_some::<E>(&err),
}
}
}
impl<T: Serialize, E: Serialize + ErrorToHttpCode> Responder for IResponse<T, E> { impl<T: Serialize, E: Serialize + ErrorToHttpCode> Responder for IResponse<T, E> {
type Body = EitherBody<String>; type Body = EitherBody<String>;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> { fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
match serde_json::to_string(&self.0) { match serde_json::to_string(&self) {
Ok(body) => { Ok(body) => {
let code = match &self.0 { let code = match &self.0 {
Ok(_) => StatusCode::OK, Ok(_) => StatusCode::OK,

11
src/routes/users/me.rs Normal file
View File

@@ -0,0 +1,11 @@
use crate::app_state::AppState;
use crate::database::models::User;
use crate::extractors::base::SyncExtractor;
use actix_web::{HttpResponse, Responder, get, web};
#[get("/me")]
pub async fn me(user: SyncExtractor<User>, app_state: web::Data<AppState>) -> impl Responder {
HttpResponse::Ok().json(user.into_inner())
}
mod schema {}

1
src/routes/users/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod me;

View File

@@ -1,25 +1,14 @@
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use crate::app_state::{app_state, AppState, Schedule}; use crate::app_state::{app_state, AppState, Schedule};
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
use actix_web::{test, web, App};
use std::sync::LazyLock;
use crate::parser::tests::test_result; use crate::parser::tests::test_result;
use actix_web::{web};
use std::sync::LazyLock;
pub fn test_env() { pub fn test_env() {
dotenvy::from_path(".env.test").expect("Failed to load test environment file"); dotenvy::from_path(".env.test").expect("Failed to load test environment file");
} }
pub async fn test_app<F>(
app_state: web::Data<AppState>,
factory: F,
) -> impl Service<actix_http::Request, Response = ServiceResponse, Error = actix_web::Error>
where
F: HttpServiceFactory + 'static,
{
test::init_service(App::new().app_data(app_state).service(factory)).await
}
pub fn test_app_state() -> web::Data<AppState> { pub fn test_app_state() -> web::Data<AppState> {
let state = app_state(); let state = app_state();
let mut schedule_lock = state.schedule.lock().unwrap(); let mut schedule_lock = state.schedule.lock().unwrap();

18
src/utility/error.rs Normal file
View File

@@ -0,0 +1,18 @@
use std::fmt::{Write};
use std::fmt::Display;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ResponseErrorMessage<T: Display> {
code: T,
message: String,
}
impl<T: Display + Serialize> ResponseErrorMessage<T> {
pub fn new(code: T) -> Self {
let mut message = String::new();
write!(&mut message, "{}", code).unwrap();
Self { code, message }
}
}

View File

@@ -1 +1,2 @@
pub mod jwt; pub mod jwt;
pub mod error;