retrofit-rs

Crates.ioretrofit-rs
lib.rsretrofit-rs
version0.2.0
created_at2025-10-05 13:03:25.18267+00
updated_at2025-10-15 17:01:29.692975+00
descriptionA type-safe, declarative HTTP client library for Rust with interceptors, inspired by Retrofit
homepagehttps://github.com/okayfine996/retrofit-rs
repositoryhttps://github.com/okayfine996/retrofit-rs
max_upload_size
id1869010
size174,024
okayfine (okayfine996)

documentation

README

🔧 Retrofit-rs

Crates.io Documentation License: MIT

A type-safe, declarative HTTP client library for Rust, inspired by Java Retrofit.

✨ Features

  • 🎯 Declarative API - Define your API with simple macros
  • 🔒 Type-Safe - Compile-time type checking
  • Async/Await - Built on tokio for high performance
  • 🔒 Blocking API - Synchronous API for CLI tools and scripts
  • 🔄 Interceptors - Powerful middleware system
  • 📝 Auto Serialization - Automatic JSON handling with serde
  • 🛡️ Memory Safe - Leveraging Rust's ownership system

🚀 Quick Start

Async API (Default)

Add Retrofit-rs to your Cargo.toml:

[dependencies]
retrofit-rs = "0.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

Define your API:

use retrofit_rs::{api, get, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct User {
    id: u64,
    name: String,
}

#[api("https://api.github.com")]
trait GitHubApi {
    #[get("/users/{username}")]
    async fn get_user(&self, username: &str) -> Result<User>;
}

#[tokio::main]
async fn main() -> Result<()> {
    let api = GitHubApiClient::new()?;
    let user = api.get_user("octocat").await?;
    println!("{:#?}", user);
    Ok(())
}

Blocking API

For simple scripts and CLI tools that don't need async:

[dependencies]
retrofit-rs = { version = "0.1", default-features = false, features = ["sync"] }
serde = { version = "1", features = ["derive"] }
# Note: No tokio needed!
use retrofit_rs::{api, get, Result};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct User {
    id: u64,
    name: String,
}

// Note: Add 'blocking' parameter and remove 'async' from methods
#[api("https://api.github.com", blocking)]
trait GitHubApi {
    #[get("/users/{username}")]
    fn get_user(&self, username: &str) -> Result<User>;
}

fn main() -> Result<()> {
    let api = GitHubApiClient::new()?;
    let user = api.get_user("octocat")?;  // No .await needed
    println!("{:#?}", user);
    Ok(())
}

💡 Examples

Basic Usage

use retrofit_rs::{Retrofit, Result};

let client = Retrofit::builder()
    .base_url("https://api.example.com")
    .build()?;

let request = client.get("/users/{id}")
    .path_param("id", "123");

let user: User = client.execute(request).await?;

Timeout Configuration

use std::time::Duration;

let client = Retrofit::builder()
    .base_url("https://api.example.com")
    .timeout(Duration::from_secs(60))          // Overall timeout (default: 30s)
    .connect_timeout(Duration::from_secs(10))  // Connection timeout (default: 10s)
    .build()?;

With Interceptors

use retrofit_rs::interceptors::{LoggingInterceptor, AuthInterceptor};

let client = Retrofit::builder()
    .base_url("https://api.example.com")
    .add_interceptor(LoggingInterceptor::body())
    .add_interceptor(AuthInterceptor::bearer("token"))
    .build()?;

Declarative API with CRUD Operations

use retrofit_rs::{api, get, post, put, delete, Body, Path, Query};

#[derive(Serialize, Deserialize)]
struct Book {
    id: u32,
    title: String,
    author: String,
}

#[api("http://localhost:3030")]
trait BookApi {
    #[get("/books")]
    async fn list_books(&self) -> Result<Vec<Book>>;

    #[get("/books/{id}")]
    async fn get_book(&self, id: Path<u32>) -> Result<Book>;

    #[post("/books")]
    async fn create_book(&self, book: Body<Book>) -> Result<Book>;

    #[put("/books/{id}")]
    async fn update_book(&self, id: Path<u32>, book: Body<Book>) -> Result<Book>;

    #[delete("/books/{id}")]
    async fn delete_book(&self, id: Path<u32>) -> Result<()>;
}

// Use the API
let client = retrofit_rs::Retrofit::builder()
    .base_url("http://localhost:3030")
    .build()?;
let api = BookApiClient::with_client(client);

// Create
let book = api.create_book(Body::new(new_book)).await?;

// Read
let book = api.get_book(Path::new(1)).await?;

// Update
let updated = api.update_book(Path::new(1), Body::new(book)).await?;

// Delete
api.delete_book(Path::new(1)).await?;

🎛️ Feature Configuration

Retrofit-rs supports flexible feature flags to control what gets compiled:

Feature Default Description
async Async HTTP client with tokio
sync Blocking (sync) HTTP client

Examples:

# Default: async
retrofit-rs = "0.1"

# Only blocking (smallest binary, no tokio)
retrofit-rs = { version = "0.1", default-features = false, features = ["sync"] }

# Both async and blocking
retrofit-rs = { version = "0.1", default-features = false, features = ["async", "sync"] }

Benefits:

  • 🎯 Compile only what you need
  • 📦 Smaller binary size (blocking only: ~30% smaller)
  • ⚡ Faster compilation
  • 🔧 Flexible configuration

See FEATURES_GUIDE.md for detailed information.

🎯 Run Examples

# Async Examples (requires 'async' feature)
cargo run -p retrofit-rs-examples --example book_crud --features retrofit-rs/async
cargo run -p retrofit-rs-examples --example github --features retrofit-rs/async

# Blocking Examples (requires 'blocking' feature)
cargo run -p retrofit-rs-examples --example github_blocking --features retrofit-rs/blocking
# Or use default features (async)
cargo run -p retrofit-rs-examples --example github

🔧 Built-in Interceptors

Logging Interceptor

use retrofit_rs::interceptors::LoggingInterceptor;

// Basic logging (method and URL)
.add_interceptor(LoggingInterceptor::basic())

// With headers
.add_interceptor(LoggingInterceptor::headers())

// Full logging (including body)
.add_interceptor(LoggingInterceptor::body())

Auth Interceptor

use retrofit_rs::interceptors::AuthInterceptor;

// Bearer token
.add_interceptor(AuthInterceptor::bearer("token"))

// Basic auth
.add_interceptor(AuthInterceptor::basic("user", "pass"))

// API Key
.add_interceptor(AuthInterceptor::api_key("X-API-Key", "key"))

🏗️ Architecture

retrofit-rs/
├── retrofit-rs           # Core library
│   ├── Client            # Async HTTP client
│   ├── Blocking          # Sync HTTP client
│   ├── Interceptors      # Middleware system
│   └── Builder           # Fluent API
│
└── retrofit-rs-macros    # Procedural macros
    ├── #[api]            # API definition (supports async & blocking)
    └── #[get/post]       # HTTP methods

📈 Comparison

Feature Java Retrofit Retrofit-rs
API Style Annotations Macros ✨
Type Safety Runtime Compile-time
Async Callback/RxJava async/await
Blocking OkHttp sync Built-in
Performance JVM overhead Zero-cost
Memory Safety GC Ownership

Async vs Blocking

Feature Async API Blocking API
Use Case Web servers, high concurrency CLI tools, scripts, simple clients
Syntax async fn, .await Regular fn, no .await
Runtime Requires tokio No runtime needed
Concurrency Non-blocking, handles many requests Blocking, one request at a time
Performance High throughput Simple and predictable
Binary Size Larger (includes runtime) Smaller (no async runtime)
Learning Curve Steeper (async concepts) Easier (familiar sync code)

🎓 Why "Retrofit-rs"?

Retrofit-rs brings the beloved Retrofit API design pattern to Rust, combining Java's proven declarative HTTP client approach with Rust's compile-time safety and zero-cost abstractions.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

Inspired by Square's Retrofit for Java.


Made with ❤️ and 🦀 by the Rust community

Commit count: 0

cargo fmt