8 Commits

Author SHA1 Message Date
cuddle-please
11d659dbd7 chore(release): 0.8.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-08-07 09:30:44 +00:00
762da1e672 feat: update readme
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
2025-08-07 11:29:29 +02:00
3bc512ab48 chore(release): v0.7.5 (#33)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
chore(release): 0.7.5

Co-authored-by: cuddle-please <bot@cuddle.sh>
Reviewed-on: https://git.front.kjuulh.io/kjuulh/mad/pulls/33
2025-07-24 22:44:46 +02:00
7d8071d41b feat: print big inner
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 22:44:13 +02:00
1cc4138ec7 chore: more error correction
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 22:36:39 +02:00
00517daaaa chore: correct error test to not be as verbose 2025-07-24 22:27:04 +02:00
b941dc9a76 chore(release): v0.7.4 (#32)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
chore(release): 0.7.4

Co-authored-by: cuddle-please <bot@cuddle.sh>
Reviewed-on: https://git.front.kjuulh.io/kjuulh/mad/pulls/32
2025-07-24 22:23:00 +02:00
5da3c83c12 feat: cleanup aggregate error for single error
All checks were successful
continuous-integration/drone/push Build is passing
2025-07-24 22:22:12 +02:00
6 changed files with 175 additions and 31 deletions

View File

@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.8.0] - 2025-08-07
### Added
- update readme
## [0.7.5] - 2025-07-24
### Added
- print big inner
### Other
- more error correction
- correct error test to not be as verbose
## [0.7.4] - 2025-07-24
### Added
- cleanup aggregate error for single error
## [0.7.3] - 2025-07-24 ## [0.7.3] - 2025-07-24
### Added ### Added

2
Cargo.lock generated
View File

@@ -278,7 +278,7 @@ dependencies = [
[[package]] [[package]]
name = "notmad" name = "notmad"
version = "0.7.2" version = "0.7.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",

View File

@@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.7.3" version = "0.8.0"
[workspace.dependencies] [workspace.dependencies]
mad = { path = "crates/mad" } mad = { path = "crates/mad" }

163
README.md
View File

@@ -1,39 +1,92 @@
# MAD # MAD - Lifecycle Manager for Rust Applications
Mad is a life-cycle manager for long running rust operations. [![Crates.io](https://img.shields.io/crates/v/notmad.svg)](https://crates.io/crates/notmad)
[![Documentation](https://docs.rs/notmad/badge.svg)](https://docs.rs/notmad)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
- Webservers ## Overview
- Queue bindings
- gRPC servers etc
- Cron runners
It is supposed to be the main thing the application runs, and everything from it is spawned and managed by it. MAD is a robust lifecycle manager designed for long-running Rust operations. It provides a simple, composable way to manage multiple concurrent services within your application, handling graceful startup and shutdown automatically.
### Perfect for:
- 🌐 Web servers
- 📨 Queue consumers and message processors
- 🔌 gRPC servers
- ⏰ Cron job runners
- 🔄 Background workers
- 📡 Any long-running async operations
## Features
- **Component-based architecture** - Build your application from composable, reusable components
- **Graceful shutdown** - Automatic handling of shutdown signals with proper cleanup
- **Concurrent execution** - Run multiple components in parallel with tokio
- **Error handling** - Built-in error propagation and logging
- **Cancellation tokens** - Coordinate shutdown across all components
- **Minimal boilerplate** - Focus on your business logic, not lifecycle management
## Installation
Add MAD to your `Cargo.toml`:
```toml
[dependencies]
notmad = "0.7.5"
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
```
## Quick Start
Here's a simple example of a component that simulates a long-running server:
```rust ```rust
struct WaitServer {} use mad::{Component, Mad};
use async_trait::async_trait;
use tokio_util::sync::CancellationToken;
// Define your component
struct WebServer {
port: u16,
}
#[async_trait] #[async_trait]
impl Component for WaitServer { impl Component for WebServer {
fn name(&self) -> Option<String> { fn name(&self) -> Option<String> {
Some("NeverEndingRun".into()) Some(format!("WebServer on port {}", self.port))
} }
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> { async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
let millis_wait = rand::thread_rng().gen_range(50..1000); println!("Starting web server on port {}", self.port);
// Simulates a server running for some time. Is normally supposed to be futures blocking indefinitely // Your server logic here
tokio::time::sleep(std::time::Duration::from_millis(millis_wait)).await; // The cancellation token will be triggered on shutdown
tokio::select! {
_ = cancellation.cancelled() => {
println!("Shutting down web server");
}
_ = self.serve() => {
println!("Server stopped");
}
}
Ok(()) Ok(())
} }
} }
impl WebServer {
async fn serve(&self) {
// Simulate a running server
tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
}
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
// Build and run your application
Mad::builder() Mad::builder()
.add(WaitServer {}) .add(WebServer { port: 8080 })
.add(WaitServer {}) .add(WebServer { port: 8081 }) // You can add multiple instances
.add(WaitServer {})
.run() .run()
.await?; .await?;
@@ -41,11 +94,75 @@ async fn main() -> anyhow::Result<()> {
} }
``` ```
## Advanced Usage
### Custom Components
Components can be anything that implements the `Component` trait:
```rust
use mad::{Component, Mad};
use async_trait::async_trait;
struct QueueProcessor {
queue_name: String,
}
#[async_trait]
impl Component for QueueProcessor {
fn name(&self) -> Option<String> {
Some(format!("QueueProcessor-{}", self.queue_name))
}
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
while !cancellation.is_cancelled() {
// Process messages from queue
self.process_next_message().await?;
}
Ok(())
}
}
```
### Error Handling
MAD provides comprehensive error handling through the `MadError` type with automatic conversion from `anyhow::Error`:
```rust
async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> {
// Errors automatically convert from anyhow::Error to MadError
database_operation().await?;
// Or return explicit errors
if some_condition {
return Err(anyhow::anyhow!("Something went wrong").into());
}
Ok(())
}
```
## Examples ## Examples
Can be found (here)[crates/mad/examples] Check out the [examples directory](crates/mad/examples) for more detailed examples:
- basic - **basic** - Simple component lifecycle
- fn - **fn** - Using functions as components
- signals - **signals** - Handling system signals
- error_log - **error_log** - Error handling and logging
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Author
Created and maintained by [kjuulh](https://github.com/kjuulh)
## Repository
Find the source code at [https://github.com/kjuulh/mad](https://github.com/kjuulh/mad)

View File

@@ -11,16 +11,16 @@ mod waiter;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum MadError { pub enum MadError {
#[error("component failed: {0}")] #[error("component: {0:#?}")]
Inner(#[source] anyhow::Error), Inner(#[source] anyhow::Error),
#[error("component(s) failed: {run}")] #[error("component: {run:#?}")]
RunError { run: anyhow::Error }, RunError { run: anyhow::Error },
#[error("component(s) failed: {close}")] #[error("component(s) failed: {close}")]
CloseError { close: anyhow::Error }, CloseError { close: anyhow::Error },
#[error("component(s) failed: {0}")] #[error("component(s): {0}")]
AggregateError(AggregateError), AggregateError(AggregateError),
#[error("setup not defined")] #[error("setup not defined")]
@@ -49,6 +49,14 @@ impl AggregateError {
impl Display for AggregateError { impl Display for AggregateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.errors.is_empty() {
return Ok(());
}
if self.errors.len() == 1 {
return f.write_str(&self.errors.first().unwrap().to_string());
}
f.write_str("MadError::AggregateError: (")?; f.write_str("MadError::AggregateError: (")?;
for error in &self.errors { for error in &self.errors {

View File

@@ -1,6 +1,6 @@
# yaml-language-server: $schema=https://git.front.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json # yaml-language-server: $schema=https://git.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json
base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-lib-plan.git" base: "git@git.kjuulh.io:kjuulh/cuddle-rust-lib-plan.git"
vars: vars:
service: "mad" service: "mad"
@@ -12,6 +12,6 @@ please:
repository: "mad" repository: "mad"
branch: main branch: main
settings: settings:
api_url: "https://git.front.kjuulh.io" api_url: "https://git.kjuulh.io"
actions: actions:
rust: rust: