想要知道現在天氣狀況並不是很困難的事,只要打開手機 App,或是在瀏覽器中搜尋就可以知道了。但是如果想要自己寫一個 CLI 小工具來取得目前地區的天氣狀況可以嗎?沒問題,Rust 可以做到任何事,這篇文章就來介紹如何用 Rust 實作一個取得目前地區天氣的 CLI 小工具。
流程簡介
這個 CLI 的流程如下:
- 使用者輸入命令列參數,參數帶入城市名稱。例如輸入 Taipei。
- 將輸入地點轉換成經緯度。
- 透過經緯度取得天氣資料。
需要申請的 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);
}
這邊使用 clap
的 Parser
來處理命令列參數,並且使用 tokio
的 main
來執行 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 元 !