mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 09:47:50 +03:00
0.5.0
Возвращёна реализация сериализации в json для IResponse Добавлены типы для экстракции данных из запросов средствами actix-web Добавлен экстрактор для получения пользователя по токену доступа передаваемому в запросе Добавлен макрос для автоматической реализации ResponseError для ошибок экстракторов Добавлен эндпоинт users/me Из главного проекта исключена зависимость actix-http посредством переноса части тестового функционала в отдельный crate
This commit is contained in:
4
.idea/schedule-parser-rusted.iml
generated
4
.idea/schedule-parser-rusted.iml
generated
@@ -5,6 +5,10 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<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" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
164
Cargo.lock
generated
164
Cargo.lock
generated
@@ -58,6 +58,15 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-macros"
|
||||
version = "0.2.4"
|
||||
@@ -120,6 +129,14 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-utils"
|
||||
version = "3.0.1"
|
||||
@@ -138,7 +155,7 @@ checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
"actix-macros",
|
||||
"actix-macros 0.2.4",
|
||||
"actix-router",
|
||||
"actix-rt",
|
||||
"actix-server",
|
||||
@@ -245,12 +262,56 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
@@ -518,6 +579,12 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.0"
|
||||
@@ -845,6 +912,29 @@ dependencies = [
|
||||
"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]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -934,6 +1024,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@@ -953,9 +1054,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1462,6 +1565,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -1477,6 +1586,30 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
@@ -1830,6 +1963,21 @@ dependencies = [
|
||||
"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]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -2203,17 +2351,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schedule-parser-rusted"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-macros 0.1.0",
|
||||
"actix-test",
|
||||
"actix-web",
|
||||
"bcrypt",
|
||||
"calamine",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"derive_more",
|
||||
"diesel",
|
||||
"diesel-derive-enum",
|
||||
"dotenvy",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"fuzzy-matcher",
|
||||
"jsonwebtoken",
|
||||
"mime",
|
||||
@@ -2772,6 +2924,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,17 +1,24 @@
|
||||
[workspace]
|
||||
members = ["actix-macros", "actix-test"]
|
||||
|
||||
[package]
|
||||
name = "schedule-parser-rusted"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.10.2"
|
||||
actix-macros = { path = "actix-macros" }
|
||||
bcrypt = "0.17.0"
|
||||
calamine = "0.26.1"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
derive_more = "2.0.1"
|
||||
diesel = { version = "2.2.8", features = ["postgres"] }
|
||||
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
||||
dotenvy = "0.15.7"
|
||||
env_logger = "0.11.7"
|
||||
futures-util = "0.3.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
|
||||
mime = "0.3.17"
|
||||
@@ -27,7 +34,7 @@ tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
|
||||
rand = "0.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = "3.10.0"
|
||||
actix-test = { path = "actix-test" }
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
|
||||
1
actix-macros/.gitignore
vendored
Normal file
1
actix-macros/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
7
actix-macros/Cargo.lock
generated
Normal file
7
actix-macros/Cargo.lock
generated
Normal 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
12
actix-macros/Cargo.toml
Normal 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
79
actix-macros/src/lib.rs
Normal 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
1
actix-test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1520
actix-test/Cargo.lock
generated
Normal file
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
8
actix-test/Cargo.toml
Normal 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
12
actix-test/src/lib.rs
Normal 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
|
||||
}
|
||||
69
src/extractors/authorized_user.rs
Normal file
69
src/extractors/authorized_user.rs
Normal 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
56
src/extractors/base.rs
Normal 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
2
src/extractors/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod authorized_user;
|
||||
pub mod base;
|
||||
27
src/main.rs
27
src/main.rs
@@ -1,33 +1,44 @@
|
||||
use crate::app_state::{AppState, app_state};
|
||||
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::users::me::me;
|
||||
use actix_web::{App, HttpServer, web};
|
||||
use dotenvy::dotenv;
|
||||
|
||||
mod app_state;
|
||||
|
||||
mod database;
|
||||
mod routes;
|
||||
|
||||
mod test_env;
|
||||
|
||||
mod utility;
|
||||
mod xls_downloader;
|
||||
|
||||
mod parser;
|
||||
mod xls_downloader;
|
||||
|
||||
mod extractors;
|
||||
mod routes;
|
||||
|
||||
mod utility;
|
||||
|
||||
mod test_env;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
unsafe { std::env::set_var("RUST_LOG", "debug") };
|
||||
env_logger::init();
|
||||
|
||||
HttpServer::new(move || {
|
||||
let auth_scope = web::scope("/auth")
|
||||
.service(sign_in_default)
|
||||
.service(sign_in_vk)
|
||||
.service(sign_up_default)
|
||||
.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))
|
||||
.unwrap()
|
||||
|
||||
@@ -132,14 +132,15 @@ mod tests {
|
||||
use crate::database::driver;
|
||||
use crate::database::models::{User, UserRole};
|
||||
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 actix_http::StatusCode;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::dev::ServiceResponse;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::test;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt::Write;
|
||||
use actix_test::test_app;
|
||||
|
||||
async fn sign_in_client(data: Request) -> ServiceResponse {
|
||||
let app = test_app(test_app_state(), sign_in_default).await;
|
||||
|
||||
@@ -208,11 +208,12 @@ mod tests {
|
||||
use crate::database::models::UserRole;
|
||||
use crate::routes::auth::sign_up::schema::Request;
|
||||
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 actix_http::StatusCode;
|
||||
use crate::test_env::tests::{static_app_state, test_app_state, test_env};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::dev::ServiceResponse;
|
||||
use actix_web::http::Method;
|
||||
use actix_web::test;
|
||||
use actix_test::test_app;
|
||||
|
||||
struct SignUpPartial {
|
||||
username: String,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod auth;
|
||||
pub mod users;
|
||||
mod schema;
|
||||
|
||||
@@ -2,7 +2,7 @@ use actix_web::body::EitherBody;
|
||||
use actix_web::error::JsonPayloadError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder};
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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> {
|
||||
type Body = EitherBody<String>;
|
||||
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
match serde_json::to_string(&self.0) {
|
||||
match serde_json::to_string(&self) {
|
||||
Ok(body) => {
|
||||
let code = match &self.0 {
|
||||
Ok(_) => StatusCode::OK,
|
||||
|
||||
11
src/routes/users/me.rs
Normal file
11
src/routes/users/me.rs
Normal 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
1
src/routes/users/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod me;
|
||||
@@ -1,25 +1,14 @@
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
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 actix_web::{web};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub fn test_env() {
|
||||
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> {
|
||||
let state = app_state();
|
||||
let mut schedule_lock = state.schedule.lock().unwrap();
|
||||
|
||||
18
src/utility/error.rs
Normal file
18
src/utility/error.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod jwt;
|
||||
pub mod error;
|
||||
Reference in New Issue
Block a user