Rust Integration Guide
Integrate OilPriceAPI into your Rust applications to access the latest available crude oil prices, Brent crude data, natural gas rates, and commodity market information. Source publication and refresh schedules vary by dataset. 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("OILPRICEAPI_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 OILPRICEAPI_KEY environment variable
pub fn from_env() -> Result<Self, OilPriceError> {
let api_key = env::var("OILPRICEAPI_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 OILPRICEAPI_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