rustapi-rs 0.1.450

A FastAPI-like web framework for Rust - DX-first, type-safe, batteries included
Documentation
#[cfg(not(any(feature = "extras-session", feature = "session")))]
fn main() {
    eprintln!(
        "Run this example with session support enabled:\n  cargo run -p rustapi-rs --example auth_api --features extras-session"
    );
}

#[cfg(any(feature = "extras-session", feature = "session"))]
use rustapi_rs::extras::session::{MemorySessionStore, Session, SessionConfig, SessionLayer};
#[cfg(any(feature = "extras-session", feature = "session"))]
use rustapi_rs::prelude::*;
#[cfg(any(feature = "extras-session", feature = "session"))]
use std::time::Duration;

#[cfg(any(feature = "extras-session", feature = "session"))]
#[derive(Debug, Deserialize, Schema)]
struct LoginRequest {
    user_id: String,
}

#[cfg(any(feature = "extras-session", feature = "session"))]
#[derive(Debug, Serialize, Schema)]
struct SessionView {
    authenticated: bool,
    user_id: Option<String>,
    refreshed: bool,
    session_id: Option<String>,
}

#[cfg(any(feature = "extras-session", feature = "session"))]
async fn session_view(session: &Session) -> SessionView {
    let user_id = session.get::<String>("user_id").await.ok().flatten();
    let refreshed = session
        .get::<bool>("refreshed")
        .await
        .ok()
        .flatten()
        .unwrap_or(false);
    let session_id = session.id().await;

    SessionView {
        authenticated: user_id.is_some(),
        user_id,
        refreshed,
        session_id,
    }
}

#[cfg(any(feature = "extras-session", feature = "session"))]
async fn login(session: Session, Json(payload): Json<LoginRequest>) -> Json<SessionView> {
    session.cycle_id().await;
    session
        .insert("user_id", &payload.user_id)
        .await
        .expect("serializing user_id into the session should succeed");
    session
        .insert("refreshed", false)
        .await
        .expect("serializing refreshed flag into the session should succeed");

    Json(session_view(&session).await)
}

#[cfg(any(feature = "extras-session", feature = "session"))]
async fn me(session: Session) -> Json<SessionView> {
    Json(session_view(&session).await)
}

#[cfg(any(feature = "extras-session", feature = "session"))]
async fn refresh(session: Session) -> Json<SessionView> {
    if session.contains("user_id").await {
        session.cycle_id().await;
        session
            .insert("refreshed", true)
            .await
            .expect("serializing refreshed flag into the session should succeed");
    }

    Json(session_view(&session).await)
}

#[cfg(any(feature = "extras-session", feature = "session"))]
async fn logout(session: Session) -> NoContent {
    session.destroy().await;
    NoContent
}

#[cfg(any(feature = "extras-session", feature = "session"))]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    println!("Starting session auth example...");
    println!(" -> POST   http://127.0.0.1:3000/auth/login   {{\"user_id\":\"demo-user\"}}");
    println!(" -> GET    http://127.0.0.1:3000/auth/me");
    println!(" -> POST   http://127.0.0.1:3000/auth/refresh");
    println!(" -> POST   http://127.0.0.1:3000/auth/logout");

    RustApi::new()
        .layer(SessionLayer::new(
            MemorySessionStore::new(),
            SessionConfig::new()
                .cookie_name("rustapi_auth")
                .secure(false)
                .ttl(Duration::from_secs(60 * 30)),
        ))
        .route("/auth/login", post(login))
        .route("/auth/me", get(me))
        .route("/auth/refresh", post(refresh))
        .route("/auth/logout", post(logout))
        .run("127.0.0.1:3000")
        .await
}