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$/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
164
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
13
Cargo.toml
13
Cargo.toml
@@ -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
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::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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod users;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|||||||
@@ -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
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)]
|
#[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
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 jwt;
|
||||||
|
pub mod error;
|
||||||
Reference in New Issue
Block a user