fr-rust 0.1.1

A comprehensive framework/utility library for Actix-web, Postgres, Redis, and authentication.
Documentation
fr-rust-0.1.1 has been yanked.

fr-rust Documentation

fr-rust is a comprehensive, high-performance web framework utility library built on top of Actix-Web. It provides out-of-the-box support for DDoS protection, standardized responses, WebSockets, Redis, Database pooling, Cryptography, and common verification services (JWT, OTP, Link Verification, Email).

1. Quick Start & Configuration

Set up your server, initialize shared states, and protect your app with the built-in DDoS shield in your main entry point.

use fr_rust::prelude::*;
// Use actix web
use actix_web::{
    App, HttpServer, web::Data as AppData, web
};
#[fr_rust::main]
async fn main() -> MainRlt {
    // 1. Load Environment Variables
    load_env();

    // 2. Configure DDoS Shield
    let ddos_shield = DdosShield::builder()
        .max_requests(5)          // Max requests per window
        .window_secs(1)           // Time window (1 second)
        .ban_duration_secs(20)    // Ban duration for violators
        .block_agent("malicious-bot")
        .allow_missing_ua(false)
        .build();

    // 3. Initialize Shared Services
    let jwt_secret = env_var("JWT_SECRET");
    let jwt = Jwt::new(jwt_secret);
    
    let email_config = EmailConfig {
        smtp_host: env_var("SMTP_HOST"),
        smtp_port: env_var("SMTP_PORT").parse().expect("Invalid SMTP_PORT"),
        smtp_user: env_var("SMTP_USER"),
        smtp_pass: env_var("SMTP_PASS"),
        from_name: env_var("FROM_NAME"),
        from_email: env_var("FROM_EMAIL"),
    };
    let email_service = EmailService::new(email_config).unwrap();
    
    let pool = DbPool::new(env_var("DATABASE_URL"));
    let redis = RedisManager::new(&env_var("REDIS_URL")).unwrap();
    
    let key = env_var("AES_KEY");
    let key_bytes: &[u8; 32] = key.as_bytes().try_into().expect("AES_KEY must be 32 bytes");
    let crypto_service = CryptoService::new(key_bytes).unwrap();
    
    let otp_service = OtpService::new(OtpConfig {
        secret: env_var("KEY"),
        crypto: crypto_service.clone(),
        redis: redis.clone(),
    });
    
    let linkv_service = LinkV::new(LinkVConfig {
        secret: env_var("KEY"),
        crypto: crypto_service.clone(),
        redis: redis.clone(),
    });
    
    let ws = WsManager::new(WsConfig { server: 1, redis: redis.clone() });

    // 4. Start the HTTP Server
    let address = format!("{}:{}", env_var_or_default("IP", "0.0.0.0"), env_var_or_default("PORT", "8080"));
    println!("Starting server at http://{}", address);
    
    HttpServer::new(move || App::new()
        .app_data(AppData::new(email_service.clone()))
        .app_data(AppData::new(pool.clone()))
        .app_data(AppData::new(redis.clone()))
        .app_data(AppData::new(crypto_service.clone()))
        .app_data(AppData::new(otp_service.clone()))
        .app_data(AppData::new(linkv_service.clone()))
        .app_data(AppData::new(jwt.clone()))
        .app_data(AppData::new(ws.clone()))
        .configure(app_config)
        .wrap(ddos_shield.clone())
    )
    .bind(address)?
    .run()
    .await
}

// Route Configuration
pub fn app_config(cfg: &mut web::ServiceConfig) {
    cfg.service(index_file);
}

2. Common Types

fr-rust exports convenient type aliases to reduce boilerplate:

Type Alias Original Type Description
Rsp HttpResponse Standard HTTP Response
Rqs HttpRequest Standard HTTP Request
Rlt Result<(), actix::Error> Standard Actix Result
MainRlt (Varies) Main Function Result
FileRlt (Varies) File Streaming Result

3. Responses & Routing

fr-rust provides utility functions to return standard HTTP responses, stream files, and parse JSON easily.

File Streaming

Stream large files directly to the client with ease.

#[get("/")]
pub async fn index_file() -> FileRlt {
    send_file("./static/index.html").await
}

Standard & JSON Responses

Response Helper Purpose Example
http_ok(msg) 200 OK with string http_ok("Success")
http_bad(msg) 400 Bad Request with string http_bad("Error")
send_str(msg) Raw string response send_str("Hello")
send_json(data) Standard JSON response (Vec/Struct) send_json(vec![1, 2])
http_ok_json(data) 200 OK with JSON map/macro http_ok_json(json!({"a": 1}))
http_bad_json(data) 400 Bad Request with JSON http_bad_json(json!({"err": true}))
Implementation Example:
#[get("/test/responses/{type}")]
async fn test_responses(path: Path<String>) -> Rsp {
    match path.into_inner().as_str() {
        "ok" => http_ok("Ok!"),
        "bad" => http_bad("Error!"),
        "str" => send_str("Hello from send_str!"),
        "json_struct" => send_json(User { id: 1, name: "Sayed".to_string() }),
        "json_vec" => send_json(vec![1, 2, 3]),
        "json_macro_bad" => http_bad_json(json!({"success": false})),
        "json_map" => {
            let mut map = HashMap::new();
            map.insert("name", "Sayed");
            http_ok_json(map)
        },
        _ => http_bad("Unknown response type requested.")
    }
}

4. WebSockets

Built on top of actix-ws, the WebSocket manager (WsManager) provides robust room management and messaging utilities. Core Methods:

// Use actix-ws with this manager
// 1. Create a high-performance unbounded channel for a user connection
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel::<String>(128);

// 2. Manage Users
ws_manager.register(user_id, tx);
ws_manager.drop_user(user_id);

// 3. Manage Rooms
ws_manager.join_room(user_id, room_name);
ws_manager.leave_room(user_id, room_name);
ws_manager.drop_room(room_name); // Note: Renamed from drop_user(room_name) to clarify intent

// 4. Send Messages
ws_manager.msg_user(user_id, "Hello User!".to_string());
ws_manager.msg_room(room_name, UserMsg::new("SenderID", "RoomID", "Hello Room!"));
ws_manager.broadcast("System Maintenance in 5 minutes!".to_string());

// 5. Query Rooms
let messages = ws_manager.get_room_msgs(room_name);

// Use the manager in actix route
#[get("/ws/{user_id}")]
async fn ws_handler(
    req: HttpRequest,
    body: web::Payload,
    ws_manager: web::Data<WsManager>,
    path: web::Path<String>,
) -> Rsp {
    let user_id = path.into_inner();

    // 1. Setup high-performance bounded channel (128 items is ideal for memory/backpressure balance)
    let (tx, mut rx) = mpsc::channel::<String>(128);

    // Perform WebSocket handshake
    let (res, mut session, mut msg_stream) = match actix_ws::handle(&req, body) {
        Ok(res) => res,
        Err(_) => return http_bad("Internal Server Error!"), 
    };

    // 2. Register user with the manager
    ws_manager.register(&user_id, tx);
    // here your code.....
}

5. Database Operations

Access your relational database easily via AppData.

  • execute: Run queries without expecting a return dataset (CREATE, INSERT, UPDATE).
  • query: Fetch multiple rows.
  • query_one: Fetch exactly one row.
  • query_opt: Fetch an optional row (returns Option).
#[get("/test/db")]
async fn test_db(pool: AppData<DbPool>) -> Rsp {
    // Execution
    pool.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT);", &[]).await.unwrap();
    pool.execute("INSERT INTO users (name) VALUES ($1);", &[&"Alice"]).await.unwrap();
    
    // Multiple Results
    let rows = pool.query("SELECT id, name FROM users;", &[]).await.unwrap();
    
    // Optional Result (Safe Fallback)
    let maybe_row = pool.query_opt("SELECT name FROM users WHERE id = $1;", &[&999]).await.unwrap();
    let fallback = match maybe_row {
        Some(r) => r.get("name"),
        None => "User 999 does not exist.".to_string(),
    };
    
    http_ok("DB Operations successful!")
}

6. Redis Integrations

Injected via AppData, the Redis client supports standard operations, TTL, Hashes, Lists, Sets, and coordinated batch Pub/Sub. Pub/Sub Example:

// Another operations same as deadpool_redis or redis-rs
// It just helps you to create redis pool auto & handle pub/sub.
use deadpool_redis::redis::AsyncCommands;
use futures_util::StreamExt;

let redis = redis_manager.get_connection().await.unwrap();

// Publish a message
redis.publish("event_name", "content").await.unwrap();

// Subscribe to a stream
let mut stream = redis.subscribe("event_name").await?;
while let Some(msg) = stream.next().await {
    let payload: String = msg.get_payload()?;
    println!("Received: {}", payload);
}

7. Verification & Notification Services

7.1 JSON Web Tokens (JWT)

let user_id = "user_12345";

// Generate token (No expiration)
let forever_token = jwt.generate_token(user_id).unwrap();

// Generate token (Expiring)
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;
let expiry_timestamp = current_time + 3600; // 1 hour from now
// Generate the token 
let expiring_token = jwt.generate_exp_token(user_id, expiry_timestamp).unwrap();

// Verify
let is_valid = jwt.verify_token(&forever_token);

// Parse Token
let real_user_id = jwt.parse_token(expiring_token).unwrap();

7.2 OTP Generation & Verification

Access via AppData.

let otp = otp_service.generate_otp("user123", 6, 300).await.unwrap(); // 6-digit OTP, 300 second expiry_time
if otp_service.verify_otp("user123", &otp).await.unwrap() {
    http_ok("Valid OTP!")
}

7.3 Link Verification Tokens

Access via AppData.

let user_id = "user_12345";

let expiring_token = linkv_service
    .generate_token(user_id, 300) // 300 sec validity_time 
    .await
    .expect("Failed to generate expiring token");
    
// Let's test the token we generated above
let verification_result = linkv_service.verify_token(user_id, &expiring_token).await.unwrap(); 

if let Ok(returned_token) = verification_result {
    println!("Valid: {}", returned_token);
} else {
    println!("Invalid token");
}

7.4 Email Service

Access via AppData.

#[get("/test/email")]
async fn test_email(email_service: AppData<EmailService>) -> Rsp {
    let data = EmailData {
        to: "receiver@example.com".to_string(),
        subject: "Hello!".to_string(),
        body: "Mail body here.".to_string(),
    };
    
    match email_service.send_email(&data).await {
        Ok(_) => http_ok("Sent!"),
        Err(_) => http_bad("Failed."),
    }
}

8. Cryptography Service

Inject AppData to securely encrypt data or hash passwords.

  • Symmetric Encryption: encrypt_text / decrypt_text
  • Password Hashing: hash_data / verify_hash (Async, highly secure)
  • Fast Hashing: sha256_hash
#[get("/test/crypto")]
async fn test_crypto(crypto: AppData<CryptoService>) -> Rsp {
    // Symmetric Encryption
    let encrypted = crypto.encrypt_text("Hello").unwrap();
    let decrypted = crypto.decrypt_text(&encrypted.encrypted_text).unwrap();
    
    // Fast Hash
    let fast_hashing = crypto.sha256_hash("password").unwrap();
    
    // Secure Password Hashing (Slower)
    let hashed = crypto.hash_data("password").await.unwrap();
    let is_valid = crypto.verify_hash("password", &hashed.hash).await.unwrap();
    
    http_ok("Crypto operations completed.")
}

9. General Utilities

Standalone utility macros and functions for rapid development.

// Capture user input directly from the terminal (Python-like)
let name = input("What's your name? ");

// Generate a random HEX-encoded key of a specific byte length
let key = generate_key(100); // 100 character random string

Important Note Regarding Error Handling

While you may encounter .unwrap() used in various code examples, it should not be used in production environments. Always ensure that errors are handled gracefully and explicitly to maintain the stability and reliability of your application.

Contact Us

If you have any questions or would like to get in touch: