Skip to content

KevalKThumar/rust_api_calling

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rust_api_calling

Crates.io Documentation License: MIT

A clean, idiomatic Rust HTTP client library with one core function that handles all HTTP communication. Built on top of reqwest with automatic JSON serialization via serde.

Features

  • Single core functionmake_request handles everything; get() and post() are thin wrappers
  • Typed responses — Deserialize JSON directly into your Rust structs via generics
  • Builder pattern — Configure the client once with ApiClient::builder(), reuse everywhere
  • Base URL support — Set it once, then use relative paths like "/users"
  • Session management — Automatic XSRF token and cookie handling across requests
  • Per-request overrides — Custom timeout, headers, and bearer token per call via RequestConfig
  • Strongly-typed errorsApiError enum with clear variants instead of magic error codes
  • Structured logging — Built-in tracing integration
  • Async/await — Powered by tokio

Installation

Add to your Cargo.toml:

[dependencies]
rust_api_calling = "0.1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }

Quick Start

use rust_api_calling::{ApiClient, ApiResponse};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Post {
    id: u32,
    title: String,
    body: String,
}

#[tokio::main]
async fn main() {
    let client = ApiClient::builder()
        .base_url("https://jsonplaceholder.typicode.com")
        .build()
        .unwrap();

    // GET request — response is automatically deserialized into Post
    let response: ApiResponse<Post> = client
        .get("/posts/1", None, None)
        .await
        .unwrap();

    println!("Post #{}: {}", response.body.id, response.body.title);
}

Usage Guide

1. Create the Client

Create one ApiClient and reuse it — it uses connection pooling internally.

use rust_api_calling::ApiClient;
use std::time::Duration;

let client = ApiClient::builder()
    .base_url("https://api.example.com")      // Prepended to all relative paths
    .default_timeout(Duration::from_secs(30))  // Default timeout for all requests
    .default_header("Accept", "application/json") // Sent with every request
    .session_enabled(true)                     // Auto-manage cookies & XSRF tokens
    .build()
    .unwrap();

2. GET Requests

use serde::Deserialize;

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

// Simple GET
let response = client.get::<Vec<User>>("/users", None, None).await?;
println!("Got {} users", response.body.len());

// GET with query parameters
let response = client
    .get::<Vec<User>>("/users", Some(&[("role", "admin"), ("limit", "10")]), None)
    .await?;

3. POST Requests

use serde::{Serialize, Deserialize};

#[derive(Serialize)]
struct CreateUser {
    name: String,
    email: String,
    password: String,
}

#[derive(Deserialize)]
struct UserResponse {
    id: String,
    name: String,
}

let new_user = CreateUser {
    name: "Keval".to_string(),
    email: "keval@example.com".to_string(),
    password: "secure123".to_string(),
};

let response = client
    .post::<UserResponse, _>("/users", Some(&new_user), None)
    .await?;

println!("Created user with ID: {}", response.body.id);

4. Per-Request Configuration

Override client defaults for individual requests:

use rust_api_calling::RequestConfig;
use std::time::Duration;

let config = RequestConfig::new()
    .timeout(Duration::from_secs(5))            // Custom timeout
    .bearer_token("eyJhbGciOiJIUzI1NiJ9...")    // Authorization header
    .header("X-Request-ID", "req-12345");        // Custom header

let response = client
    .get::<serde_json::Value>("/protected/resource", None, Some(config))
    .await?;

5. Error Handling

All errors are strongly typed via the ApiError enum:

use rust_api_calling::ApiError;

match client.get::<serde_json::Value>("/users/999", None, None).await {
    Ok(response) => {
        println!("Status: {}", response.status);
        println!("Body: {:?}", response.body);
    }
    Err(ApiError::HttpError { status, body }) => {
        // Server returned 4xx or 5xx
        eprintln!("HTTP {}: {}", status, body);
    }
    Err(ApiError::Timeout) => {
        eprintln!("Request timed out!");
    }
    Err(ApiError::NetworkError(e)) => {
        eprintln!("Network error: {}", e);
    }
    Err(ApiError::InvalidUrl(url)) => {
        eprintln!("Bad URL: {}", url);
    }
    Err(e) => {
        eprintln!("Other error: {}", e);
    }
}

6. Using Raw JSON Responses

If you don't want to define a struct, use serde_json::Value:

let response = client
    .get::<serde_json::Value>("/users", None, None)
    .await?;

// Access fields dynamically
if let Some(users) = response.body.as_array() {
    for user in users {
        println!("Name: {}", user["name"]);
    }
}

// Raw body string is also available
println!("Raw JSON: {}", response.raw_body);

7. Absolute URLs (Bypass Base URL)

You can always pass a full URL to bypass the configured base_url:

// This ignores the base_url and calls the full URL directly
let response = client
    .get::<serde_json::Value>("https://other-api.com/data", None, None)
    .await?;

8. Session Management

When session_enabled(true) is set, the client automatically:

  • Reads XSRF tokens from response headers (x-xsrf-token, xsrf-token, x-csrf-token)
  • Reads cookies from set-cookie headers
  • Attaches them to all subsequent requests

You can also manage sessions manually:

// Set session data manually
client.session.set_xsrf_token("my-token");
client.session.set_cookie("session=abc123");

// Read current session data
if let Some(token) = client.session.xsrf_token() {
    println!("Current XSRF token: {}", token);
}

// Clear all session data
client.session.clear();

9. The Core Function

All methods delegate to make_request. You can call it directly for full control:

use rust_api_calling::HttpMethod;

let response = client
    .make_request::<serde_json::Value, _>(
        HttpMethod::Post,
        "/endpoint",
        Some(&my_body),       // Request body (serialized to JSON)
        Some(&[("key", "val")]), // Query parameters
        Some(config),         // Per-request config
    )
    .await?;

10. Response Object

Every response includes:

let response = client.get::<MyType>("/endpoint", None, None).await?;

response.status;     // HTTP status code (u16)
response.headers;    // HashMap<String, String>
response.body;       // Deserialized body (MyType)
response.raw_body;   // Raw response string

response.is_success();            // true if 2xx
response.header("content-type");  // Case-insensitive header lookup

API Reference

ApiClient

Method Description
ApiClient::builder() Start building a new client
.get(url, query, config) Perform a GET request
.post(url, body, config) Perform a POST request
.make_request(method, url, body, query, config) The core function — full control

ApiClientBuilder

Method Description
.base_url(url) Set the base URL for all relative paths
.default_timeout(duration) Set default timeout (default: 60s)
.default_header(key, value) Add a header sent with every request
.session_enabled(bool) Enable automatic session/cookie management
.build() Build the ApiClient

RequestConfig

Method Description
RequestConfig::new() Create empty per-request config
.timeout(duration) Override timeout for this request
.bearer_token(token) Set Authorization bearer token
.header(key, value) Add a custom header

ApiError

Variant When
InvalidUrl(String) URL could not be parsed
NetworkError(reqwest::Error) Connection, DNS, or TLS failure
SerializationError(serde_json::Error) JSON serialize/deserialize failure
HttpError { status, body } Server returned non-2xx status
EmptyResponse Server returned empty body
Timeout Request timed out

Logging

This library uses the tracing crate for structured logging. To see log output, add a subscriber in your application:

// Add to dev-dependencies: tracing-subscriber = { version = "0.3", features = ["env-filter"] }

tracing_subscriber::fmt()
    .with_env_filter("rust_api_calling=debug")
    .init();

Log levels used:

  • INFO — Request method and URL
  • DEBUG — Query params, request body, response body
  • WARN — Empty responses
  • ERROR — Network errors, timeouts, HTTP errors, deserialization failures

Example

A full working example is included in the examples/ directory:

cargo run --example usage

License

Licensed under the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages