| Crates.io | http-provider-macro |
| lib.rs | http-provider-macro |
| version | 0.1.2 |
| created_at | 2025-06-15 07:00:20.177438+00 |
| updated_at | 2025-06-16 04:20:48.772939+00 |
| description | A procedural macro for generating type-safe HTTP client providers |
| homepage | |
| repository | https://github.com/azeem-0/http-provider-macro |
| max_upload_size | |
| id | 1713030 |
| size | 90,975 |
A Rust procedural macro that generates HTTP client providers with compile-time endpoint definitions. This macro eliminates boilerplate code for creating HTTP clients by automatically generating methods for your API endpoints.
{param} syntaxAdd this to your Cargo.toml:
[dependencies]
http-provider-macro = "0.1.0"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
use http_provider_macro::http_provider;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
#[derive(Serialize)]
struct CreateUserRequest {
name: String,
email: String,
}
// Define your HTTP provider
http_provider!(
UserApiProvider,
{
{
path: "/users",
method: GET,
res: Vec<User>,
},
{
path: "/users",
method: POST,
req: CreateUserRequest,
res: User,
},
{
path: "/users/{id}",
method: GET,
path_params: PathParams,
res: User,
}
}
);
#[derive(Serialize)]
struct PathParams {
id: u32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url = reqwest::Url::parse("https://api.example.com")?;
let client = UserApiProvider::new(base_url, 30); // 30 second timeout
// GET /users - auto-generated method name: get_users
let users = client.get_users().await?;
println!("Users: {:?}", users);
// POST /users - auto-generated method name: post_users
let new_user = client.post_users(&CreateUserRequest {
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
}).await?;
println!("Created user: {:?}", new_user);
// GET /users/{id} - auto-generated method name: get_users_id
let user = client.get_users_id(&PathParams { id: 1 }).await?;
println!("User: {:?}", user);
Ok(())
}
Each endpoint is defined within braces {} with the following fields:
path: The API endpoint path (string literal)method: HTTP method (GET, POST, PUT, DELETE)res: Response type that implements Deserializefn_name: Custom function name (defaults to auto-generated)req: Request body type that implements Serializeheaders: Header type (typically reqwest::header::HeaderMap)query_params: Query parameters type that implements Serializepath_params: Path parameters type with fields matching {param} in pathuse reqwest::header::HeaderMap;
http_provider!(
ApiProvider,
{
{
path: "/protected/data",
method: GET,
fn_name: fetch_protected_data,
res: ApiResponse,
headers: HeaderMap,
}
}
);
// Usage
let mut headers = HeaderMap::new();
headers.insert("Authorization", "Bearer token123".parse()?);
let data = client.fetch_protected_data(headers).await?;
#[derive(Serialize)]
struct SearchQuery {
q: String,
limit: u32,
offset: u32,
}
http_provider!(
SearchProvider,
{
{
path: "/search",
method: GET,
query_params: SearchQuery,
res: SearchResults,
}
}
);
// Usage
let results = client.get_search(&SearchQuery {
q: "rust".to_string(),
limit: 10,
offset: 0,
}).await?;
#[derive(Serialize)]
struct ResourcePath {
user_id: u32,
resource_id: String,
}
http_provider!(
ResourceProvider,
{
{
path: "/users/{user_id}/resources/{resource_id}",
method: GET,
path_params: ResourcePath,
res: Resource,
}
}
);
// Usage
let resource = client.get_users_user_id_resources_resource_id(&ResourcePath {
user_id: 123,
resource_id: "abc-def".to_string(),
}).await?;
http_provider!(
CompleteProvider,
{
{
path: "/api/v1/users/{user_id}/posts",
method: POST,
fn_name: create_user_post,
path_params: UserPath,
req: CreatePostRequest,
res: Post,
headers: HeaderMap,
query_params: PostQuery,
}
}
);
// Usage
let post = client.create_user_post(
&UserPath { user_id: 123 },
&CreatePostRequest { title: "Hello".to_string() },
headers,
&PostQuery { draft: false },
).await?;
The macro generates:
url, client, and timeout fieldsnew(url: reqwest::Url, timeout: u64) -> SelfGenerated methods follow this pattern:
pub async fn method_name(
&self,
path_params: &PathParamsType, // if path_params specified
body: &RequestType, // if req specified
headers: HeaderMap, // if headers specified
query: &QueryType, // if query_params specified
) -> Result<ResponseType, String>
When fn_name is not specified, names are generated as:
{method}_{path} where path slashes become underscoresGET /users → get_usersPOST /api/v1/posts → post_api_v1_postsPUT /users/{id} → put_users_idAll generated methods return Result<T, String> where errors include:
Licensed under either of
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.