Rust Integration Guide
Integrate OilPriceAPI into your Rust applications to access real-time crude oil prices, Brent crude data, natural gas rates, and commodity market information. Perfect for high-performance systems, CLI tools, and async services.
Dependencies
Add these to your Cargo.toml:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
Quick Start
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde::Deserialize;
use std::env;
#[derive(Debug, Deserialize)]
struct Price {
code: String,
price: f64,
currency: String,
formatted: String,
}
#[derive(Debug, Deserialize)]
struct PriceResponse {
status: String,
data: Price,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("OILPRICE_API_KEY")?;
let client = reqwest::Client::new();
let response: PriceResponse = client
.get("https://api.oilpriceapi.com/v1/prices/latest")
.query(&[("by_code", "WTI_USD")])
.header(AUTHORIZATION, format!("Token {}", api_key))
.send()
.await?
.json()
.await?;
println!("WTI Price: {}", response.data.formatted);
Ok(())
}
Complete API Client
Create a full-featured async client:
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::env;
use std::time::Duration;
use thiserror::Error;
// Error types
#[derive(Error, Debug)]
pub enum OilPriceError {
#[error("HTTP request failed: {0}")]
RequestError(#[from] reqwest::Error),
#[error("API error ({status}): {message}")]
ApiError { status: u16, message: String },
#[error("Invalid API key")]
Unauthorized,
#[error("Commodity not found: {0}")]
NotFound(String),
#[error("Rate limit exceeded")]
RateLimited,
#[error("Environment variable not found: {0}")]
EnvError(#[from] std::env::VarError),
}
// Response types
#[derive(Debug, Deserialize, Clone)]
pub struct Price {
pub code: String,
pub price: f64,
pub currency: String,
pub unit: String,
pub formatted: String,
pub created_at: String,
#[serde(default)]
pub change_24h: Option<f64>,
#[serde(default)]
pub change_24h_pct: Option<f64>,
}
#[derive(Debug, Deserialize)]
struct PriceResponse {
status: String,
data: Price,
}
#[derive(Debug, Deserialize, Clone)]
pub struct HistoricalPrice {
pub date: String,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: Option<i64>,
}
#[derive(Debug, Deserialize)]
struct HistoricalData {
code: String,
currency: String,
unit: String,
prices: Vec<HistoricalPrice>,
}
#[derive(Debug, Deserialize)]
struct HistoricalResponse {
status: String,
data: HistoricalData,
}
#[derive(Debug, Deserialize)]
pub struct Commodity {
pub code: String,
pub name: String,
pub category: String,
pub currency: String,
pub unit: String,
}
#[derive(Debug, Deserialize)]
struct CommoditiesResponse {
status: String,
data: Vec<Commodity>,
}
#[derive(Debug, Deserialize)]
struct ErrorResponse {
error: ErrorDetail,
}
#[derive(Debug, Deserialize)]
struct ErrorDetail {
code: String,
message: String,
}
// API Client
pub struct OilPriceClient {
client: Client,
api_key: String,
base_url: String,
}
impl OilPriceClient {
/// Create a new client with the given API key
pub fn new(api_key: impl Into<String>) -> Self {
Self {
client: Client::builder()
.timeout(Duration::from_secs(30))
.build()
.expect("Failed to create HTTP client"),
api_key: api_key.into(),
base_url: "https://api.oilpriceapi.com/v1".to_string(),
}
}
/// Create a client using OILPRICE_API_KEY environment variable
pub fn from_env() -> Result<Self, OilPriceError> {
let api_key = env::var("OILPRICE_API_KEY")?;
Ok(Self::new(api_key))
}
/// Get latest price for a commodity
pub async fn get_latest_price(&self, code: &str) -> Result<Price, OilPriceError> {
let url = format!("{}/prices/latest", self.base_url);
let response = self
.client
.get(&url)
.query(&[("by_code", code)])
.header("Authorization", format!("Token {}", self.api_key))
.header("X-Client-Name", "rust-client")
.header("X-Client-Version", "1.0.0")
.send()
.await?;
self.handle_response::<PriceResponse>(response, code)
.await
.map(|r| r.data)
}
/// Get multiple prices at once
pub async fn get_prices(&self, codes: &[&str]) -> Result<Vec<Price>, OilPriceError> {
let futures: Vec<_> = codes.iter().map(|code| self.get_latest_price(code)).collect();
let results = futures::future::join_all(futures).await;
results.into_iter().collect()
}
/// Get historical prices for a commodity
pub async fn get_historical_prices(
&self,
code: &str,
start_date: Option<&str>,
end_date: Option<&str>,
) -> Result<Vec<HistoricalPrice>, OilPriceError> {
let url = format!("{}/prices/historical", self.base_url);
let mut request = self
.client
.get(&url)
.query(&[("by_code", code)])
.header("Authorization", format!("Token {}", self.api_key));
if let Some(start) = start_date {
request = request.query(&[("start_date", start)]);
}
if let Some(end) = end_date {
request = request.query(&[("end_date", end)]);
}
let response = request.send().await?;
self.handle_response::<HistoricalResponse>(response, code)
.await
.map(|r| r.data.prices)
}
/// Get all available commodities
pub async fn get_commodities(&self) -> Result<Vec<Commodity>, OilPriceError> {
let url = format!("{}/commodities", self.base_url);
let response = self
.client
.get(&url)
.header("Authorization", format!("Token {}", self.api_key))
.send()
.await?;
self.handle_response::<CommoditiesResponse>(response, "commodities")
.await
.map(|r| r.data)
}
// Handle HTTP response and errors
async fn handle_response<T: for<'de> Deserialize<'de>>(
&self,
response: reqwest::Response,
context: &str,
) -> Result<T, OilPriceError> {
let status = response.status();
match status {
StatusCode::OK => Ok(response.json().await?),
StatusCode::UNAUTHORIZED => Err(OilPriceError::Unauthorized),
StatusCode::NOT_FOUND => Err(OilPriceError::NotFound(context.to_string())),
StatusCode::TOO_MANY_REQUESTS => Err(OilPriceError::RateLimited),
_ => {
let body = response.text().await.unwrap_or_default();
Err(OilPriceError::ApiError {
status: status.as_u16(),
message: body,
})
}
}
}
}
Usage Examples
Basic Usage
use oilpriceapi::OilPriceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = OilPriceClient::from_env()?;
// Get WTI price
let wti = client.get_latest_price("WTI_USD").await?;
println!("WTI: {} ({:+.2}%)",
wti.formatted,
wti.change_24h_pct.unwrap_or(0.0)
);
// Get Brent price
let brent = client.get_latest_price("BRENT_CRUDE_USD").await?;
println!("Brent: {}", brent.formatted);
Ok(())
}
Concurrent Requests
use futures::future::join_all;
use oilpriceapi::OilPriceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = OilPriceClient::from_env()?;
let codes = vec!["WTI_USD", "BRENT_CRUDE_USD", "NATURAL_GAS_USD", "DIESEL_USD"];
// Fetch all prices concurrently
let futures: Vec<_> = codes
.iter()
.map(|code| client.get_latest_price(code))
.collect();
let results = join_all(futures).await;
for (code, result) in codes.iter().zip(results.iter()) {
match result {
Ok(price) => println!("{}: {}", code, price.formatted),
Err(e) => eprintln!("{}: Error - {}", code, e),
}
}
Ok(())
}
Historical Data Analysis
use oilpriceapi::OilPriceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = OilPriceClient::from_env()?;
// Get last 30 days of WTI prices
let prices = client
.get_historical_prices("WTI_USD", Some("2025-01-01"), Some("2025-01-30"))
.await?;
// Calculate statistics
let closes: Vec<f64> = prices.iter().map(|p| p.close).collect();
let avg = closes.iter().sum::<f64>() / closes.len() as f64;
let min = closes.iter().cloned().fold(f64::INFINITY, f64::min);
let max = closes.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
println!("WTI Statistics (30 days):");
println!(" Average: ${:.2}", avg);
println!(" Min: ${:.2}", min);
println!(" Max: ${:.2}", max);
println!(" Range: ${:.2}", max - min);
Ok(())
}
Error Handling
use oilpriceapi::{OilPriceClient, OilPriceError};
async fn get_price_safe(client: &OilPriceClient, code: &str) -> Option<f64> {
match client.get_latest_price(code).await {
Ok(price) => Some(price.price),
Err(OilPriceError::Unauthorized) => {
eprintln!("Invalid API key - check OILPRICE_API_KEY");
None
}
Err(OilPriceError::NotFound(code)) => {
eprintln!("Commodity not found: {}", code);
None
}
Err(OilPriceError::RateLimited) => {
eprintln!("Rate limited - waiting before retry");
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
// Retry once
client.get_latest_price(code).await.ok().map(|p| p.price)
}
Err(e) => {
eprintln!("API error: {}", e);
None
}
}
}
Retry with Exponential Backoff
use std::time::Duration;
use tokio::time::sleep;
async fn with_retry<T, F, Fut>(
operation: F,
max_retries: u32,
) -> Result<T, OilPriceError>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, OilPriceError>>,
{
let mut attempt = 0;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(OilPriceError::RateLimited) if attempt < max_retries => {
attempt += 1;
let delay = Duration::from_secs(2u64.pow(attempt));
println!("Rate limited. Retrying in {:?}...", delay);
sleep(delay).await;
}
Err(e) => return Err(e),
}
}
}
// Usage
let client = OilPriceClient::from_env()?;
let price = with_retry(|| client.get_latest_price("WTI_USD"), 3).await?;
CLI Tool Example
Create a simple CLI for fetching prices:
use clap::Parser;
use oilpriceapi::OilPriceClient;
#[derive(Parser)]
#[command(name = "oilprice")]
#[command(about = "Fetch oil and commodity prices")]
struct Cli {
/// Commodity codes to fetch
#[arg(required = true)]
codes: Vec<String>,
/// Output as JSON
#[arg(short, long)]
json: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let client = OilPriceClient::from_env()?;
for code in &cli.codes {
match client.get_latest_price(code).await {
Ok(price) => {
if cli.json {
println!("{}", serde_json::to_string_pretty(&price)?);
} else {
println!("{}: {} ({})",
price.code,
price.formatted,
price.created_at
);
}
}
Err(e) => eprintln!("Error fetching {}: {}", code, e),
}
}
Ok(())
}
Actix-web Integration
use actix_web::{get, web, App, HttpResponse, HttpServer};
use oilpriceapi::OilPriceClient;
use std::sync::Arc;
struct AppState {
oil_client: OilPriceClient,
}
#[get("/prices/{code}")]
async fn get_price(
data: web::Data<Arc<AppState>>,
path: web::Path<String>,
) -> HttpResponse {
let code = path.into_inner();
match data.oil_client.get_latest_price(&code).await {
Ok(price) => HttpResponse::Ok().json(price),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let client = OilPriceClient::from_env().expect("API key required");
let state = Arc::new(AppState { oil_client: client });
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(state.clone()))
.service(get_price)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Common Commodity Codes
| Code | Description |
|---|---|
WTI_USD | West Texas Intermediate |
BRENT_CRUDE_USD | Brent Crude |
NATURAL_GAS_USD | Henry Hub Natural Gas |
HEATING_OIL_USD | Heating Oil |
DIESEL_USD | Ultra Low Sulfur Diesel |
GASOLINE_USD | RBOB Gasoline |
Related
- API Reference - Full endpoint documentation
- Error Codes - Complete error handling guide
- Rate Limits - Request limits by plan