用 Rust 作一個取得目前地區天氣的 CLI 小工具

#rust
用 Rust 作一個取得目前地區天氣的 CLI 小工具
五倍技術部
技術文章
用 Rust 作一個取得目前地區天氣的 CLI 小工具

想要知道現在天氣狀況並不是很困難的事,只要打開手機 App,或是在瀏覽器中搜尋就可以知道了。但是如果想要自己寫一個 CLI 小工具來取得目前地區的天氣狀況可以嗎?沒問題,Rust 可以做到任何事,這篇文章就來介紹如何用 Rust 實作一個取得目前地區天氣的 CLI 小工具。

流程簡介

這個 CLI 的流程如下:

  1. 使用者輸入命令列參數,參數帶入城市名稱。例如輸入 Taipei。
  2. 將輸入地點轉換成經緯度。
  3. 透過經緯度取得天氣資料。

需要申請的 API

安裝必要套件

這個專案會需要以下的套件:

clap = { version = "4.4.6", features = ["derive"] }
dotenv = "0.15.0"
reqwest = { version = "0.11.22", features = ["json"] }
serde_json = "1.0.107"
tokio = { version = "1.33.0", features = ["full"] }
  • clap:處理命令列參數。
  • dotenv:讀取 .env
  • reqwest:發送 HTTP 請求。
  • serde_json:轉換 JSON 格式。
  • tokio:處理非同步。

取得經緯度

新增一個 get_coordinates 的函式,用來取得經緯度:

use dotenv::dotenv;
use std::env;

#[derive(Debug)]
enum MyError {
    MissingField(String),
    ReqwestError(reqwest::Error),
}

impl From<reqwest::Error> for MyError {
    fn from(err: reqwest::Error) -> MyError {
        MyError::ReqwestError(err)
    }
}

async fn get_coordinates(city_name: &str) -> Result<(f64, f64), MyError> {
    dotenv().ok();

    let api_key = env::var("GOOGLE_MAPS_API_KEY").expect("GOOGLE_MAPS_API_KEY must be set");
    let url = format!(
        "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}",
        city_name, api_key
    );

    let response: serde_json::Value = reqwest::get(&url).await?.json().await?;

    let lat = response["results"][0]["geometry"]["location"]["lat"]
        .as_f64()
        .ok_or(MyError::MissingField(
            "Latitude is missing or not a float".to_string(),
        ))?;
    let lng = response["results"][0]["geometry"]["location"]["lng"]
        .as_f64()
        .ok_or(MyError::MissingField(
            "Longitude is missing or not a float".to_string(),
        ))?;

    Ok((lat, lng))
}

這一段程式碼主要是在做從城市名稱取得經緯度的事情,這邊使用了 dotenv 來讀取 .env 檔案,並且使用 reqwest 來發送 HTTP 請求,最後使用 serde_json 來轉換 JSON 格式。

處理命令列參數

接下來要處理命令列參數,這邊使用 clap 來處理:

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Opts {
    #[clap(short, long)]
    city: String,
}

#[tokio::main]
async fn main() {
    let opts = Opts::parse();
    process_city(&opts.city).await;
}

async fn process_city(city_name: &str) {
    match get_coordinates(city_name).await {
        Ok((lat, lng)) => println!("lat: {}, lng: {}", lat, lng),
        Err(e) => println!("Error: {:?}", e),
    }
    println!("Processing city: {}", city_name);
}

這邊使用 clapParser 來處理命令列參數,並且使用 tokiomain 來執行 main 函式。

就可以在終端機中執行:

$ cargo run -- --city Taipei

或是

$ cargo run -- -c Taipei

就可以看到結果了:

❯ cargo run -- -c Taipei
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/weather-cli -c Taipei`
lat: 25.0329636, lng: 121.5654268
Processing city: Taipei

取得天氣資料

接下來要取得天氣資料,使用 OpenWeatherMap 的 API key 跟 API URL 放到 .env 檔案中:

OPEN_WEATHER_API_KEY=放入你的 API KEY
OPEN_WEATHER_API_URL=放入你的 API URL

接下來要新增一個 get_weather 的函式,用來取得天氣資料:

async fn get_weather(lat: u32, lon: u32) -> Result<(String, f64), MyError> {
    dotenv().ok();

    let api_key = env::var("OPEN_WEATHER_API_KEY").expect("OPEN_WEATHER_API_KEY must be set");
    let api_url = env::var("OPEN_WEATHER_API_URL").expect("OPEN_WEATHER_API_URL must be set");
    let url = format!(
        "{}lat={}&lon={}&appid={}&units=metric&exclude=hourly,daily",
        api_url, lat, lon, api_key
    );

    let response: serde_json::Value = reqwest::get(&url).await?.json().await?;
    let weather_description = response["current"]["weather"][0]["description"]
        .as_str()
        .unwrap()
        .to_string();
    let temperature = response["current"]["temp"].as_f64().unwrap();
    Ok((weather_description, temperature))
}

然後在 process_city 中呼叫 get_weather

async fn process_city(city_name: &str) {
    match get_coordinates(city_name).await {
        Ok((lat, lng)) => match get_weather(lat as u32, lng as u32).await {
            Ok((weather_description, temperature)) => {
                println!("City: {}\nLatitude: {:.2}, Longitude: {:.2}\nCurrent Weather: {}\nTemperature: {:.2}°C",
                city_name, lat, lng, weather_description, temperature);
            }
            Err(e) => println!("Error: {:?}", e),
        },
        Err(e) => println!("Error: {:?}", e),
    }
}

在終端機中執行:

$ cargo run -- -c Taipei

就可以看到結果了:

City: Taipei
Latitude: 25.03, Longitude: 121.57
Current Weather: scattered clouds
Temperature: 28.51°C

以上就是用 Rust 實作一個取得目前地區天氣的 CLI 小工具。如果想瞭解更多的話,可以參考五倍學院的教學影片。

📚 五倍學院全新線上直播課程 | 為你自己學 Rust 🦀
帶你認識 Rust 語言的核心概念,從資料型態、變數與常數、函數和閉包,帶你全面掌握 Rust 獨特使用性,用更安全又高效的方式處理記憶體,打造更好的應用程式
✅日期:1/15 (一) & 1/17 (三)
✅時間:19:00 - 22:00
✅立即報名:https://5xcamp.us/AfgkdR
✅原價 NT 1,450 元,限時優惠價 NT 999 元 !