Creating a Gas Price Tracker with Rust

November 2, 2024

Building my first Rust script was both challenging and rewarding! In this project, I tackled a Gas Price Tracker that fetches current gas prices from nearby stations and sorts them by cash and credit prices. Here’s an in-depth breakdown of how I approached the problem and organized the code.

RustCorpos Lots of enterprises are now implementing strucures that can withstand large capacities. Rust is one of those structures.

Project Overview

The gas price tracker script leverages the power of Rust and integrates with an external API to retrieve gas station data based on location. Using async requests with the reqwest library and structured data handling with serde, the script fetches, organizes, and displays gas prices in descending order by cash price.

How It Works

1. Sending the Query

The script starts by sending a POST request to https://www.gasbuddy.com/graphql, supplying a search term for a specific location (e.g., "61401 Galesburg"). The data is sent in a structured format, defined by Rust structs, which makes it easy to handle and serialize.

Here's how the query payload is structured:

[derive(Serialize)]
struct LocationBySearchTermPayload {
operationName: String,
variables: LocationVariables,
query: String,
}

#[derive(Serialize)]
struct LocationVariables {
maxAge: u32,
search: String,
}

LocationBySearchTermPayload:

This struct represents the payload sent in the POST request, including the operation name, variables (like search term and max age), and the query string itself.

LocationVariables:

A nested struct containing the search parameters for the API request.

2. Handling the Response

After sending the request, we need to deserialize the JSON response into Rust structs for easy data manipulation. The response is structured to include gas station information, which we capture in the following structs: #[derive(Deserialize)]

data: LocationData,
}

#[derive(Deserialize, Debug)]
struct LocationData {
locationBySearchTerm: LocationSearchResult,
}

#[derive(Deserialize, Debug)]
struct LocationSearchResult {
stations: StationResult,
trends: Vec<Trend>,
}

#[derive(Deserialize, Debug)]
struct StationResult {
results: Vec<StationInfo>,
}

#[derive(Deserialize, Debug)]
struct StationInfo {
name: String,
address: Address,
prices: Prices,
}

#[derive(Deserialize)]
struct LocationResponse {
    data: LocationData,
}

#[derive(Deserialize, Debug)]
struct LocationData {
    locationBySearchTerm: LocationSearchResult,
}

#[derive(Deserialize, Debug)]
struct LocationSearchResult {
    stations: StationResult,
    trends: Vec<Trend>,
}

#[derive(Deserialize, Debug)]
struct StationResult {
    results: Vec<StationInfo>,
}

#[derive(Deserialize, Debug)]
struct StationInfo {
    name: String,
    address: Address,
    prices: Prices,
}
  1. Fetching and Sorting Gas Prices The main function initiates the process by calling the API and sorting the results:
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let client = Client::new();
let location_data = get_location_by_search_term(&client, "61401 Galesburg").await?;
let mut gas_stations = location_data.locationBySearchTerm.stations.results;

    // Sort stations by cash price
    gas_stations.sort_by(|a, b| {
        let a_price = a.prices.cash.as_ref().and_then(|p| p.price).unwrap_or(0.0);
        let b_price = b.prices.cash.as_ref().and_then(|p| p.price).unwrap_or(0.0);
        b_price.partial_cmp(&a_price).unwrap_or(std::cmp::Ordering::Equal)

    });

This Rust project provided hands-on experience with API integration, async programming, and data manipulation. By building this gas price tracker, I learned the strengths of Rust's type safety and performance, especially in handling complex data structures and ensuring efficient, real-time responses.