Migration Guide
Migration Guide for Ignitia
π Table of Contents
Migration Guideπ
This guide helps you migrate to Ignitia from other Rust web frameworks and navigate version upgrades within Ignitia itself.
Migrating from Other Frameworksπ
From Actix-webπ
Ignitia shares many concepts with Actix-web, making migration relatively straightforward.
Basic Server Setupπ
Before (Actix-web):
use actix_web::{web, App, HttpServer, Result};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello))
.route("/users/{id}", web::get().to(get_user))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
async fn hello() -> Result<String> {
Ok("Hello World!".to_string())
}
async fn get_user(path: web::Path<u32>) -> Result<String> {
Ok(format!("User ID: {}", path.into_inner()))
}
After (Ignitia v0.2.4+):
use ignitia::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let router = Router::new()
.get("/", hello)
.get("/users/{id}", get_user);
let addr = "127.0.0.1:8080".parse()?;
Server::new(router, addr).ignitia().await
}
async fn hello() -> &'static str {
"Hello World!"
}
async fn get_user(Path(id): Path<u32>) -> String {
format!("User ID: {}", id)
}
Middleware Migrationπ
Before (Actix-web):
use actix_web::{middleware::Logger, App};
App::new()
.wrap(Logger::default())
.wrap(actix_cors::Cors::default())
After (Ignitia v0.2.4+):
use ignitia::prelude::*;
Router::new()
.middleware(LoggerMiddleware::new())
.middleware(CorsMiddleware::new().build()?)
JSON Handlingπ
Before (Actix-web):
async fn create_user(user: web::Json<CreateUser>) -> Result<web::Json<User>> {
let new_user = User {
id: 1,
name: user.name.clone(),
email: user.email.clone(),
};
Ok(web::Json(new_user))
}
After (Ignitia v0.2.4+):
async fn create_user(Json(user): Json<CreateUser>) -> impl IntoResponse {
let new_user = User {
id: 1,
name: user.name,
email: user.email,
};
Response::json(new_user) // Infallible in v0.2.4+
}
From Axumπ
Ignitia v0.2.4+ adopts middleware patterns inspired by Axum, making migration even smoother.
Basic Router Setupπ
Before (Axum):
use axum::{
extract::Path,
response::Json,
routing::{get, post},
Router,
};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(root))
.route("/users/{id}", get(get_user))
.route("/users", post(create_user));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
After (Ignitia v0.2.4+):
use ignitia::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let router = Router::new()
.get("/", root)
.get("/users/{id}", get_user)
.post("/users", create_user);
let addr = "127.0.0.1:8080".parse()?;
Server::new(router, addr).ignitia().await
}
Middleware (Axum-compatible in v0.2.4)π
Axum:
use axum::{
middleware::{self, Next},
response::Response,
http::Request,
};
async fn my_middleware(req: Request<Body>, next: Next<Body>) -> Response {
println!("Request: {}", req.uri());
next.run(req).await
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(my_middleware));
Ignitia v0.2.4+ (Same Pattern!):
use ignitia::prelude::*;
use ignitia::middleware::from_fn;
let my_middleware = from_fn(|req, next| async move {
println!("Request: {}", req.uri.path());
next.run(req).await
});
let router = Router::new()
.get("/", handler)
.middleware(my_middleware);
State Managementπ
Axum:
async fn list_users(State(state): State<Arc<AppState>>) -> Json<Vec<User>> {
// Use state
}
let app = Router::new()
.route("/users", get(list_users))
.with_state(shared_state);
Ignitia v0.2.4+:
async fn list_users(State(state): State<AppState>) -> impl IntoResponse {
// Use state
Response::json(users)
}
let router = Router::new()
.state(app_state)
.get("/users", list_users);
From Rocketπ
Route Definitionsπ
Before (Rocket):
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
#[get("/users/<id>")]
fn get_user(id: u32) -> String {
format!("User ID: {}", id)
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index, get_user])
}
After (Ignitia v0.2.4+):
use ignitia::prelude::*;
#[tokio::main]
async fn main() -> Result<()> {
let router = Router::new()
.get("/", index)
.get("/users/{id}", get_user);
let addr = "127.0.0.1:8080".parse()?;
Server::new(router, addr).ignitia().await
}
async fn index() -> &'static str {
"Hello, world!"
}
async fn get_user(Path(id): Path<u32>) -> String {
format!("User ID: {}", id)
}
JSON Guardsπ
Before (Rocket):
#[post("/users", data = "<user>")]
fn create_user(user: Json<User>) -> Json<User> {
Json(user.into_inner())
}
After (Ignitia v0.2.4+):
async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
Response::json(user) // Infallible
}
From Warpπ
Filter-based to Router-basedπ
Before (Warp):
use warp::Filter;
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name));
let routes = warp::get().and(hello);
warp::serve(routes)
.run((, 3030))
.await;
After (Ignitia v0.2.4+):
use ignitia::prelude::*;
let router = Router::new()
.get("/hello/{name}", hello);
Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await
Ignitia Version Migrationsπ
v0.2.3 to v0.2.4π
This is a major update with breaking changes. Ignitia v0.2.4 introduces a new middleware system inspired by Axum and several ergonomic improvements.
Breaking Changes Summaryπ
- Middleware API: Changed from
before/aftertohandle(req, next) - Handler Trait: Renamed to
UniversalHandler - Response API:
Response::json()now returnsResponsedirectly (notResult) - Removed Middleware:
AuthMiddlewareandErrorHandlerMiddlewareremoved - New Feature:
IntoResponsetrait for automatic error conversion
1. Middleware API Migrationπ
Old (v0.2.3):
use async_trait::async_trait;
#[async_trait]
impl Middleware for MyMiddleware {
async fn before(&self, req: &mut Request) -> Result<()> {
// Modify request
req.headers.insert("X-Custom", "value".parse().unwrap());
Ok(())
}
async fn after(&self, req: &Request, res: &mut Response) -> Result<()> {
// Modify response
res.headers.insert("X-Processed", "true".parse().unwrap());
Ok(())
}
}
New (v0.2.4+, Axum-inspired):
impl Middleware for MyMiddleware {
async fn handle(&self, mut req: Request, next: Next) -> Response {
// Modify request
req.headers.insert("X-Custom", "value".parse().unwrap());
// Call next middleware/handler
let mut response = next.run(req).await;
// Modify response
response.headers.insert("X-Processed", "true".parse().unwrap());
response
}
}
Using from_fn (Recommended for simple cases):
use ignitia::middleware::from_fn;
let my_middleware = from_fn(|mut req, next| async move {
req.headers.insert("X-Custom", "value".parse().unwrap());
let mut response = next.run(req).await;
response.headers.insert("X-Processed", "true".parse().unwrap());
response
});
router.middleware(my_middleware)
2. Response API Migrationπ
Old (v0.2.3):
async fn handler() -> Result<Response> {
let data = MyData { field: "value" };
Ok(Response::json(data)?) // Double Result
}
New (v0.2.4+):
async fn handler() -> Result<Response> {
let data = MyData { field: "value" };
Ok(Response::json(data)) // Single Result, infallible
}
// Or even simpler with IntoResponse:
async fn handler() -> impl IntoResponse {
let data = MyData { field: "value" };
Response::json(data) // No Result wrapper needed!
}
3. Error Handling Migrationπ
Old (v0.2.3):
// Required ErrorHandlerMiddleware
let router = Router::new()
.middleware(ErrorHandlerMiddleware::new())
.get("/users/{id}", get_user);
async fn get_user(Path(id): Path<u64>) -> Result<Response> {
let user = database::find_user(id)
.await
.map_err(|_| Error::not_found("User not found"))?;
Ok(Response::json(user)?)
}
New (v0.2.4+):
// No error middleware needed - automatic via IntoResponse
let router = Router::new()
.get("/users/{id}", get_user);
async fn get_user(Path(id): Path<u64>) -> Result<Response> {
let user = database::find_user(id)
.await
.ok_or_else(|| Error::not_found("User not found"))?;
Ok(Response::json(user)) // Errors auto-convert to responses
}
4. Auth Middleware Migrationπ
Old (v0.2.3):
let router = Router::new()
.middleware(AuthMiddleware::new(secret_key))
.get("/admin", admin_handler);
New (v0.2.4+):
use ignitia::middleware::from_fn;
let auth_middleware = from_fn(|req, next| async move {
if let Some(token) = req.headers.get("Authorization") {
if verify_token(token) {
return next.run(req).await;
}
}
Response::new().with_status(StatusCode::UNAUTHORIZED)
});
let router = Router::new()
.middleware(auth_middleware)
.get("/admin", admin_handler);
5. Handler Signature Updatesπ
Old (v0.2.3):
// Implementing Handler trait
impl Handler for MyHandler {
async fn handle(&self, req: Request) -> Result<Response> {
Ok(Response::json(data)?)
}
}
New (v0.2.4+):
// UniversalHandler automatically implemented for functions
async fn my_handler() -> impl IntoResponse {
Response::json(data)
}
// Or with extractors
async fn my_handler(
Path(id): Path<u64>,
Json(body): Json<CreateData>
) -> Result<impl IntoResponse> {
Ok(Response::json(data))
}
6. Complete Migration Exampleπ
Before (v0.2.3):
use ignitia::prelude::*;
#[async_trait]
struct LoggingMiddleware;
#[async_trait]
impl Middleware for LoggingMiddleware {
async fn before(&self, req: &mut Request) -> Result<()> {
println!("Request: {}", req.uri);
Ok(())
}
async fn after(&self, _req: &Request, res: &mut Response) -> Result<()> {
println!("Response: {}", res.status);
Ok(())
}
}
async fn get_user(Path(id): Path<u64>) -> Result<Response> {
let user = fetch_user(id).await?;
Ok(Response::json(user)?)
}
#[tokio::main]
async fn main() -> Result<()> {
let router = Router::new()
.middleware(LoggingMiddleware)
.middleware(ErrorHandlerMiddleware::new())
.middleware(AuthMiddleware::new(secret))
.get("/users/{id}", get_user);
Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await
}
After (v0.2.4+):
use ignitia::prelude::*;
use ignitia::middleware::from_fn;
async fn get_user(Path(id): Path<u64>) -> Result<Response> {
let user = fetch_user(id).await?;
Ok(Response::json(user)) // Infallible now!
}
#[tokio::main]
async fn main() -> Result<()> {
// Logging middleware using from_fn
let logging = from_fn(|req, next| async move {
println!("Request: {}", req.uri.path());
let response = next.run(req).await;
println!("Response: {}", response.status);
response
});
// Auth middleware using from_fn
let auth = from_fn(|req, next| async move {
if let Some(token) = req.headers.get("Authorization") {
if verify_token(token) {
return next.run(req).await;
}
}
Response::new().with_status(StatusCode::UNAUTHORIZED)
});
let router = Router::new()
.middleware(logging)
.middleware(auth) // No ErrorHandlerMiddleware needed!
.get("/users/{id}", get_user);
Server::new(router, "127.0.0.1:8080".parse()?).ignitia().await
}
Migration Checklist for v0.2.4π
- Update
Cargo.tomlto version0.2.4 - Replace
#[async_trait]middleware withhandle(req, next)pattern - Remove
?from allResponse::json()calls - Remove
ErrorHandlerMiddlewareusage - Replace
AuthMiddlewarewithfrom_fnimplementation - Update custom middleware to use
handlemethod - Change handler returns to use
impl IntoResponsewhere beneficial - Test all endpoints thoroughly
- Update tests to match new API
v0.1.x to v0.2.xπ
Major Changesπ
- Improved HTTP/2 Support: Enhanced configuration options
- WebSocket API Refinements: Simplified handler creation
- Middleware System Updates: More flexible middleware composition
- Performance Optimizations: Better connection handling
Handler Function Signaturesπ
v0.1.x:
async fn handler(req: Request) -> Result<Response> {
Ok(Response::text("Hello"))
}
v0.2.x:
async fn handler() -> Result<Response> {
Ok(Response::text("Hello"))
}
// Or with extractors
async fn handler(Json(data): Json<MyData>) -> Result<Response> {
Ok(Response::json(data))
}
WebSocket Handler Updatesπ
v0.1.x:
struct MyHandler;
impl WebSocketHandler for MyHandler {
async fn handle_connection(&self, ws: WebSocketConnection) -> Result<()> {
while let Some(msg) = ws.recv().await {
ws.send(Message::text("Echo")).await?;
}
Ok(())
}
}
v0.2.x:
let handler = websocket_handler(|ws| async move {
while let Some(msg) = ws.recv().await {
ws.send(Message::text("Echo")).await?;
}
Ok(())
});
Server Configurationπ
v0.1.x:
let server = Server::new(router, addr)
.enable_http2(true)
.run().await?;
v0.2.x:
let config = ServerConfig {
http2: Http2Config {
enabled: true,
max_concurrent_streams: Some(1000),
..Default::default()
},
..Default::default()
};
let server = Server::new(router, addr)
.with_config(config)
.ignitia().await?;
Common Migration Patternsπ
Error Handling (v0.2.4+)π
Using IntoResponse:
async fn handler() -> Result<impl IntoResponse, Error> {
let data = some_operation()
.await
.map_err(|e| Error::internal(format!("Operation failed: {}", e)))?;
Ok(Response::json(data))
}
// Errors automatically convert to HTTP responses
async fn handler2() -> Result<Response> {
Err(Error::not_found("Resource not found")) // Auto-converts to 404
}
Middleware Conversion (v0.2.4+)π
Simple Middleware:
use ignitia::middleware::from_fn;
let simple = from_fn(|req, next| async move {
println!("Before handler");
let response = next.run(req).await;
println!("After handler");
response
});
Complex Middleware:
struct ComplexMiddleware {
config: Arc<Config>,
}
impl Middleware for ComplexMiddleware {
async fn handle(&self, req: Request, next: Next) -> Response {
// Pre-processing
if !self.validate_request(&req) {
return Response::new()
.with_status(StatusCode::BAD_REQUEST);
}
// Execute handler
let mut response = next.run(req).await;
// Post-processing
response.headers.insert("X-Custom", "value".parse().unwrap());
response
}
}
State Managementπ
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
cache: Arc<RwLock<Cache>>,
}
let app_state = AppState {
db: Arc::new(Database::new()),
cache: Arc::new(RwLock::new(Cache::new())),
};
let router = Router::new()
.state(app_state)
.get("/data", get_data);
async fn get_data(State(state): State<AppState>) -> Result<Response> {
let data = state.db.fetch_data().await?;
Ok(Response::json(data))
}
Breaking Changesπ
v0.2.4 Breaking Changesπ
- Middleware Trait: Changed from
before/aftertohandle(req, next) - Response API:
Response::json()is now infallible - Handler Trait: Renamed to
UniversalHandler - Removed Middleware:
AuthMiddlewareandErrorHandlerMiddleware - Error Handling: Automatic via
IntoResponsetrait
v0.2.3 Breaking Changesπ
- Handler Signatures: Direct request parameter removed
- WebSocket API: Simplified handler creation
- Middleware Trait: Updated method signatures
- Error Types: Consolidated error system
Migration Checklistπ
Pre-Migrationπ
- Review current framework usage patterns
- Identify custom middleware and handlers
- Document current API endpoints
- Set up test environment
- Backup current codebase
- Read CHANGELOG for your target version
During Migration (v0.2.4)π
- Update
Cargo.tomltoignitia = "0.2.4" - Replace all
#[async_trait]middleware implementations - Update middleware from
before/aftertohandle(req, next) - Remove
?fromResponse::json()calls - Remove
ErrorHandlerMiddlewareusage - Replace
AuthMiddlewarewithfrom_fn - Update handler return types to use
impl IntoResponse - Convert custom middleware to new pattern
- Update tests for new API
Post-Migrationπ
- Run comprehensive tests
- Performance benchmarking
- Update documentation
- Deploy to staging environment
- Monitor for issues
- Update CI/CD pipelines
Troubleshootingπ
Common Issues (v0.2.4)π
βMethod before not foundβ Errorπ
Issue: Old middleware implementation.
Solution: Update to new handle method:
impl Middleware for MyMiddleware {
async fn handle(&self, req: Request, next: Next) -> Response {
// Your logic here
next.run(req).await
}
}
βExpected Result, found Responseβ Errorπ
Issue: Response::json() no longer returns Result.
Solution: Remove the ? operator:
// Old
Ok(Response::json(data)?)
// New
Ok(Response::json(data))
βErrorHandlerMiddleware not foundβ Errorπ
Issue: Middleware removed in v0.2.4.
Solution: Remove it - errors now auto-convert via IntoResponse:
// Old
router.middleware(ErrorHandlerMiddleware::new())
// New - just remove it!
router // Errors handle automatically
βAuthMiddleware not foundβ Errorπ
Issue: Middleware removed in v0.2.4.
Solution: Use from_fn to create custom auth:
let auth = from_fn(|req, next| async move {
if let Some(token) = req.headers.get("Authorization") {
if verify_token(token) {
return next.run(req).await;
}
}
Response::new().with_status(StatusCode::UNAUTHORIZED)
});
router.middleware(auth)
Middleware Not Workingπ
Issue: Middleware not being called.
Solution: Ensure proper handle implementation:
impl Middleware for MyMiddleware {
async fn handle(&self, req: Request, next: Next) -> Response {
// Must call next.run() for handler to execute
next.run(req).await
}
}
State Not Availableπ
Issue: State extractor failing in handlers.
Solution: Ensure state is properly registered:
let router = Router::new()
.state(my_state) // Must register before routes
.get("/endpoint", handler);
Performance Considerationsπ
- Connection Pooling: Use Ignitiaβs state management for database connections
- Middleware Order: Optimize for early rejection (auth β validation β handler)
- HTTP/2 Configuration: Tune settings for your use case
- from_fn Performance: Zero-cost abstraction, use freely
Getting Helpπ
- Check the documentation
- Review Changelog for version details
- Review examples for usage examples
- Check Middleware for middleware usage
- Submit issues on GitHub
- Join community discussions
This migration guide covers all major version transitions in Ignitia. For specific use cases not covered here, please refer to the framework documentation or reach out to the community for assistance.