So I'm brand new to the language (just installed 6 days ago) and I finally have a working project: a simple CLI that allows you to get the weather for the city/state of your choosing. I'd like feedback on the code if anyone would be so gracious as to provide some. I don't really feel great about the error handling but I am really struggling with that aspect at the moment.
The project is set up like this:
weather_cli/
-src/
--api_call.rs
--main.rs
--user_input.rs
-Cargo.toml
Cargo.toml:
[package]
name = "weather_cli"
version = "0.1.0"
authors = ["Matt Helm <myemail>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.19"
reqwest = { version = "0.11.0", features = ["blocking", "json"] }
serde = { version = "1.0.123", features = ["derive"] }
serde_json = "1.0.61"
structopt = "0.3.21"
api_call.rs
use std::env;
use reqwest::blocking;
use serde_json::Value;
pub fn get_weather(cty: &Vec<String>, st: &Vec<String>) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
let url = "https://api.openweathermap.org/data/2.5/weather";
let api_key = env::var("OPEN_WEATHER_API_KEY")?;
let city:String = cty.join(" ");
let state: String = st.join("");
let params = [
("q", city + "," + &state),
("appid", api_key),
("units", String::from("imperial"))
];
let url = reqwest::Url::parse_with_params(url, ¶ms)?;
let res = blocking::get(url)?.text()?;
let v: Value = serde_json::from_str(&res)?;
Ok(v)
}
user_input.rs
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "weather")]
pub struct Cli {
#[structopt(long, required=true, help="The name of the city for which you want to get the weather")]
pub city: Vec<String>,
#[structopt(long, required=true, help="The state in which the city is located")]
pub state: Vec<String>,
#[structopt(short, long, help="Adds sunrise/sunset times to results")]
pub daylight: bool
}
pub fn get_user_input() -> Cli {
let cli = Cli::from_args();
return cli
}
main.rs
mod api_call;
use chrono::offset::Utc;
use chrono::TimeZone;
use chrono::Local;
mod user_input;
fn main() {
let input = user_input::get_user_input();
let jsonval = api_call::get_weather(&input.city, &input.state);
match jsonval {
Ok(v) => {
if v["cod"] == 200 {
println!("Current Conditions: {}", v["weather"][0]["description"]);
println!("Temperature: {}°", v["main"]["temp"]);
println!("Feels Like: {}°", v["main"]["feels_like"]);
println!("Humidity: {}%", v["main"]["humidity"]);
println!("Wind: {} mph", v["wind"]["speed"]);
println!("Gust: {} mph", v["wind"]["gust"]);
if input.daylight {
let sunrise = match v["sys"]["sunrise"].as_i64() {
Some(num) => Utc.timestamp(num, 0).with_timezone(&Local).time().format("%I:%M:%p"),
None => Utc.timestamp(0,0).with_timezone(&Local).time().format("%I:%M:%p")
};
let sunset = match v["sys"]["sunset"].as_i64() {
Some(num) => Utc.timestamp(num, 0).with_timezone(&Local).time().format("%I:%M:%p"),
None => Utc.timestamp(0,0).with_timezone(&Local).time().format("%I:%M:%p")
};
println!("Sunrise: {}", sunrise);
println!("Sunset: {}", sunset);
}
} else {
println!("Error: {}", v["message"])
}
}
Err(err) => println!("{:#?}", err)
}
}