Skip to content
12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ repository = "https://github.com/spring-rs/spring-rs"
[workspace.dependencies]
aide = "0.16.0-alpha.1"
anyhow = "1.0"
apalis = "0.7"
apalis-redis = "0.7"
apalis-sql = "0.7"
apalis = "1.0.0-rc.1"
apalis-redis = "1.0.0-rc.1"
apalis-amqp = "1.0.0-rc.1"
apalis-postgres = "1.0.0-rc.1"
apalis-mysql = "1.0.0-rc.1"
apalis-sqlite = "1.0.0-rc.1"
apalis-workflow = "0.1.0-rc.1"
apalis-cron = "1.0.0-rc.1"
apalis-board = "1.0.0-rc.1"
async-trait = "0.1.81"
axum = "0.8"
byte-unit = "5.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/apalis-redis-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors.workspace = true
spring = { path = "../../spring" }
spring-web = { path = "../../spring-web" }
spring-redis = { path = "../../spring-redis" }
spring-apalis = { path = "../../spring-apalis", features = ["redis"] }
spring-apalis = { path = "../../spring-apalis", features = ["redis", "board", "board-web"] }
tokio = { workspace = true, features = ["full"] }
anyhow = { workspace = true }
serde = { workspace = true }
Expand Down
47 changes: 32 additions & 15 deletions examples/apalis-redis-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
use spring_apalis::apalis::{
layers::{ErrorHandlingLayer, WorkerBuilderExt as _},
prelude::{Context, Monitor, Worker, WorkerBuilder, WorkerFactoryFn as _},
};
use spring_apalis::apalis_redis::RedisStorage;
use serde::{Deserialize, Serialize};
use spring::{
app::AppBuilder,
auto_config,
plugin::{ComponentRegistry, MutableComponentRegistry},
tracing, App,
};
use spring_apalis::apalis_board::axum::{
framework::{ApiBuilder, RegisterRoute},
sse::{TracingBroadcaster, TracingSubscriber},
ui::ServeUI,
};
use spring_apalis::apalis_redis::RedisStorage;
use spring_apalis::{apalis::prelude::*, ApalisConfigurator as _, ApalisPlugin};
use spring_redis::{Redis, RedisPlugin};
use spring_web::{
axum::response::IntoResponse, extractor::Component, get, WebConfigurator, WebPlugin,
axum::{response::IntoResponse, Extension, Router},
extractor::Component,
get, WebConfigurator, WebPlugin,
};
use std::time::Duration;

#[derive(Debug, Serialize, Deserialize)]
struct LongRunningJob {}

async fn long_running_task(_task: LongRunningJob, worker: Worker<Context>) {
async fn long_running_task(_task: LongRunningJob, worker: WorkerContext) {
loop {
tracing::info!("is_shutting_down: {}", worker.is_shutting_down());
if worker.is_shutting_down() {
Expand All @@ -37,15 +40,29 @@ fn long_running_task_register(app: &mut AppBuilder, monitor: Monitor) -> Monitor
let storage = RedisStorage::new(redis);
app.add_component(storage.clone());

let worker = WorkerBuilder::new("long_running")
.layer(ErrorHandlingLayer::new())
.enable_tracing()
.rate_limit(5, Duration::from_secs(1))
.concurrency(2)
.backend(storage)
.build_fn(long_running_task);
let broadcaster = TracingBroadcaster::create();
let line_sub = TracingSubscriber::new(&broadcaster);
app.add_layer(line_sub.layer());

let api = ApiBuilder::new(Router::new())
.register(storage.clone())
.build();
let router = Router::new()
.nest("/api/v1", api)
.fallback_service(ServeUI::new())
.layer(Extension(broadcaster.clone()));
app.add_router(router.into());

monitor.register(worker)
monitor.register(move |_| {
let storage = storage.clone();
WorkerBuilder::new("long_running")
.backend(storage)
.enable_tracing()
.catch_panic()
.concurrency(2)
.rate_limit(5, Duration::from_secs(1))
.build(long_running_task)
})
}

#[auto_config(WebConfigurator)]
Expand Down
47 changes: 22 additions & 25 deletions examples/web-middleware-example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use spring::{auto_config, App};
use spring_sqlx::sqlx::Row;
use spring_sqlx::{sqlx, ConnectPool, SqlxPlugin};
use spring_web::error::KnownWebError;
use spring_web::{middlewares, WebConfigurator};
use spring_web::get;
use spring_web::nest;
use spring_web::{
axum::{
body,
Expand All @@ -15,11 +16,10 @@ use spring_web::{
extractor::Request,
WebPlugin,
};
use spring_web::{middlewares, WebConfigurator};
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
use spring_web::get;
use tower_http::cors::CorsLayer;
use spring_web::nest;
use tower_http::timeout::TimeoutLayer;

#[auto_config(WebConfigurator)]
#[tokio::main]
Expand All @@ -40,9 +40,11 @@ async fn main() {
/// queries the database for its version. The `error_request` route demonstrates error handling.
#[middlewares(
middleware::from_fn(problem_middleware),
TimeoutLayer::new(Duration::from_secs(10))
TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(10))
)]
mod routes {
use spring_web::axum::http::StatusCode;

use super::*;

#[get("/")]
Expand All @@ -64,7 +66,6 @@ mod routes {
async fn error_request() -> Result<String> {
Err(KnownWebError::bad_request("request error"))?
}

}

/// ProblemDetail: https://www.rfc-editor.org/rfc/rfc7807
Expand Down Expand Up @@ -107,10 +108,12 @@ async fn problem_middleware(
#[middlewares(
middleware::from_fn(logging_middleware),
middleware::from_fn(auth_middleware),
TimeoutLayer::new(Duration::from_secs(10)),
TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(10)),
CorsLayer::permissive()
)]
mod protected_routes {
use spring_web::axum::http::StatusCode;

use super::*;

#[get("/protected")]
Expand All @@ -119,22 +122,19 @@ mod protected_routes {
}
}

async fn logging_middleware(
request: Request,
next: Next,
) -> Response {
async fn logging_middleware(request: Request, next: Next) -> Response {
println!("🔍 [LOGGING] {} {}", request.method(), request.uri().path());
let response = next.run(request).await;
println!("✅ [LOGGING] Response completed");
response
}

async fn auth_middleware(
request: Request,
next: Next,
) -> Response {
println!("🔐 [AUTH] Checking authentication for: {}", request.uri().path());
async fn auth_middleware(request: Request, next: Next) -> Response {
println!(
"🔐 [AUTH] Checking authentication for: {}",
request.uri().path()
);

if request.headers().get("Authorization").is_none() {
return Response::builder()
.status(401)
Expand All @@ -147,26 +147,24 @@ async fn auth_middleware(

/// Example #3:
/// Middlewares can also be applied to specific routes within a module.
/// This example demonstrates how to use the `middlewares` macro to apply
/// middlewares to a specific route and apply the module's middlewares to the
/// This example demonstrates how to use the `middlewares` macro to apply
/// middlewares to a specific route and apply the module's middlewares to the
/// method router too.

#[middlewares(
middleware::from_fn(logging_middleware),
middleware::from_fn(auth_middleware),
TimeoutLayer::new(Duration::from_secs(10)),
TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_secs(10)),
CorsLayer::permissive()
)]
#[nest("/api")]
mod api {

use spring_web::extractor::Path;
use spring_web::{axum::http::StatusCode, extractor::Path};

use super::*;

#[middlewares(
middleware::from_fn(problem_middleware)
)]
#[middlewares(middleware::from_fn(problem_middleware))]
#[get("/hello")]
#[get("/hello/")]
#[get("/hello/{user}")]
Expand Down Expand Up @@ -202,4 +200,3 @@ async fn another_route() -> impl IntoResponse {
async fn goodbye_world() -> impl IntoResponse {
"goodbye world"
}

6 changes: 6 additions & 0 deletions spring-apalis/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.4.2

- **changed**: upgrade `apalis` 0.7 to 1.0.0 ([#199])

[#199]: https://github.com/spring-rs/spring-rs/pull/199

## 0.4.1

- **changed**: upgrade `schemars` 0.9 to 1.1 ([#197])
Expand Down
27 changes: 19 additions & 8 deletions spring-apalis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "spring-apalis"
description = "Integration of rust application framework spring-rs and apalis"
version = "0.4.1"
version = "0.4.2"
categories = ["date-and-time"]
keywords = ["cron-scheduler", "task-scheduling", "cron-job", "spring"]
edition.workspace = true
Expand All @@ -10,16 +10,27 @@ authors.workspace = true
repository.workspace = true

[features]
redis = ["apalis-redis"]
sql-postgres = ["apalis-sql", "apalis-sql/postgres"]
sql-sqlite = ["apalis-sql", "apalis-sql/sqlite"]
sql-mysql = ["apalis-sql", "apalis-sql/mysql"]
full = ["apalis/full"]
redis = ["apalis-redis", "spring-redis"]
sql-postgres = ["apalis-postgres", "spring-sqlx"]
sql-sqlite = ["apalis-sqlite", "spring-sqlx"]
sql-mysql = ["apalis-mysql", "spring-sqlx"]
workflow = ["apalis-workflow"]
board = ["apalis-board"]
board-web = ["apalis-board/web"]

[dependencies]
spring = { path = "../spring", version = "0.4" }
tower-service = { workspace = true }
tower-layer = { workspace = true }
tokio = { workspace = true }
apalis = { workspace = true, features = ["limit"] } # Limit for concurrency
apalis-redis = { workspace = true, optional = true } # Use redis for persistence
apalis-sql = { workspace = true, optional = true } # Use redis for persistence
apalis = { workspace = true }
apalis-redis = { workspace = true, optional = true } # Use redis for persistence
#apalis-amqp = { workspace = true, optional = true } # Use amqp for persistence
apalis-postgres = { workspace = true, optional = true } # Use postgres for persistence
apalis-sqlite = { workspace = true, optional = true } # Use sqlite for persistence
apalis-mysql = { workspace = true, optional = true } # Use mysql for persistence
apalis-workflow = { workspace = true, optional = true } # workflow
apalis-board = { workspace = true, optional = true, features = ["axum"] } # board
spring-redis = { path = "../spring-redis", optional = true }
spring-sqlx = { path = "../spring-sqlx", optional = true }
28 changes: 22 additions & 6 deletions spring-apalis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ use spring::{
};

pub use apalis;
#[cfg(feature = "board")]
pub use apalis_board;
#[cfg(feature = "sql-mysql")]
pub use apalis_mysql;
#[cfg(feature = "sql-postgres")]
pub use apalis_postgres;
#[cfg(feature = "redis")]
pub use apalis_redis;
#[cfg(any(
feature = "sql-postgres",
feature = "sql-sqlite",
feature = "sql-mysql"
))]
pub use apalis_sql;
#[cfg(feature = "sql-sqlite")]
pub use apalis_sqlite;

pub struct ApalisPlugin;

Expand All @@ -34,6 +36,20 @@ impl Plugin for ApalisPlugin {
}
}
}

#[cfg(feature = "redis")]
fn dependencies(&self) -> Vec<&str> {
vec![std::any::type_name::<spring_redis::RedisPlugin>()]
}

#[cfg(any(
feature = "sql-postgres",
feature = "sql-sqlite",
feature = "sql-mysql"
))]
fn dependencies(&self) -> Vec<&str> {
vec![std::any::type_name::<spring_sqlx::SqlxPlugin>()]
}
}

impl ApalisPlugin {
Expand Down
3 changes: 1 addition & 2 deletions spring-macros/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ fn get_prefix(input: &syn::DeriveInput) -> syn::Result<syn::LitStr> {
let attr = input
.attrs
.iter()
.filter(|attr| attr.path().is_ident("config_prefix"))
.next_back();
.rfind(|attr| attr.path().is_ident("config_prefix"));

if let Some(syn::Attribute {
meta: syn::Meta::NameValue(name_value),
Expand Down
8 changes: 4 additions & 4 deletions spring-macros/src/inject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ enum InjectableType {
Config(syn::Path),
ComponentRef(syn::Path),
ConfigRef(syn::Path),
FuncCall(syn::ExprCall),
PrototypeArg(syn::Type),
FuncCall(Box<syn::ExprCall>),
PrototypeArg(Box<syn::Type>),
}

impl InjectableType {
Expand Down Expand Up @@ -113,7 +113,7 @@ impl Injectable {
}
}
if is_prototype {
Ok(InjectableType::PrototypeArg(field.ty.clone()))
Ok(InjectableType::PrototypeArg(Box::new(field.ty.clone())))
} else {
let field_name = &field
.ident
Expand Down Expand Up @@ -168,7 +168,7 @@ impl InjectableAttr {
}
}
Self::Config => InjectableType::Config(ty.clone()),
Self::FuncCall(func_call) => InjectableType::FuncCall(func_call),
Self::FuncCall(func_call) => InjectableType::FuncCall(Box::new(func_call)),
})
}
}
Expand Down
6 changes: 5 additions & 1 deletion spring-web/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::config::{
};
use crate::Router;
use anyhow::Context;
use axum::http::StatusCode;
use spring::error::Result;
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -46,7 +47,10 @@ pub(crate) fn apply_middleware(mut router: Router, middleware: Middlewares) -> R
}
if let Some(TimeoutRequestMiddleware { enable, timeout }) = middleware.timeout_request {
if enable {
router = router.layer(TimeoutLayer::new(Duration::from_millis(timeout)));
router = router.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_millis(timeout),
));
}
}
if let Some(LimitPayloadMiddleware { enable, body_limit }) = middleware.limit_payload {
Expand Down
Loading