rustconf-code 0.1.0
A command-line interface to the openweathermap.org API
USAGE:
rustconf-code [OPTIONS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-c, --config <config> Config filename; a JSON file with
an `api_key` field
[default: openweather_api.json]
$ ./target/debug/rustconf-code --confiig cfg.json
error: Found argument '--confiig' which wasn't expected,
or isn't valid in this context
Did you mean --config?
USAGE:
rustconf-code --config <config>
For more information try --help
$ ./target/debug/rustconf-code --config bad-schema.json
Error: Failed to deserialize configuration JSON
Caused by:
invalid type: map, expected a string at line 2 column 14
running 1 test test tests::str_test ... FAILED failures: ---- tests::str_test stdout ---- thread 'tests::str_test' panicked at 'assertion failed: `(left == right)` Diff < left / right > : <"Hello, RustConf!" >"Hello, RustConf 2020!" ', src/main.rs:11:9 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
use std::env;
fn main() {
let user = env::var("USER").unwrap();
if user == "becca" {
println!("Hello, Rebecca!");
} else {
println!("Hello, {}!", user);
}
}
$ cargo build
Compiling rustconf-code v0.1.0 (~/rustconf/rustconf-code)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
$ ./target/debug/rustconf-code
Hello, Rebecca!
$ USER=nell ./target/debug/rustconf-code
Hello, nell!
$ env USER= ./target/debug/rustconf-code
Hello, !
$ env USER="$(printf '\xc3\x28')" ./target/debug/rustconf-code
thread 'main' panicked at 'called `Result::unwrap()` on an
`Err` value: NotUnicode("\xC3(")', src/main.rs:4:16
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::env;
fn main() {
let user = env::var("USER");
match user {
Ok(user) => {
if user == "becca" {
println!("Hello, Rebecca!");
} else {
println!("Hello, {}!", user);
}
}
Err(_) => println!("I couldn't figure out who you are!"),
}
}
$ env USER="$(printf '\xc3\x28')" ./target/debug/rustconf-code
I couldn't figure out who you are!
import json
import requests
with open("openweather_api.json") as f:
api_key_obj = json.load(f)
api_key = api_key_obj["api_key"]
res = requests.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": "Waltham,MA,US", "appid": api_key},
)
print(res.text)
$ ./openweather.py
{"coord":{"lon":-71.24,"lat":42.38},"weather":[{"id":801,"main":
"Clouds","description":"few clouds","icon":"02d"}],"base":
"stations","main":{"temp":298.14,"feels_like":297.91,"temp_min":
296.48,"temp_max":299.82,"pressure":1009,"humidity":57}, ...
use serde_json::Value;
fn main() {
let api_key_json = include_str!("../openweather_api.json");
let api_key_obj: Value =
serde_json::from_str(api_key_json).unwrap();
let api_key = api_key_obj
.as_object()
.unwrap()
.get("api_key")
.unwrap()
.as_str()
.unwrap();
println!("API key is {}", api_key);
}
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
struct OpenWeatherConfig {
api_key: String,
}
trait From<T> {
fn from(T) -> Self;
}
impl From<String> for OpenWeatherConfig {
fn from(key: String) -> Self {
Self {
api_key: key,
}
}
}
trait Into<T> {
fn into(self) -> T;
}
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
impl From<String> for OpenWeatherConfig {
fn from(key: String) -> Self {
Self {
api_key: key,
}
}
}
let key: String = "92c2321c2e66af5bb3aaf07bdbfe9cb7".to_owned();
let cfg: OpenWeatherConfig = key.into();
let key: &str = "92c2321c2e66af5bb3aaf07bdbfe9cb7";
let key_owned: String = key.to_owned();
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
struct OpenWeatherConfig {
api_key: String,
}
use serde::Deserialize;
fn main() {
let config_json = include_str!("../openweather_api.json");
let config: OpenWeatherConfig =
serde_json::from_str(config_json).unwrap();
println!("{:?}", config);
}
$ cat openweather_api.json
{
"api_key": "1b13e6aa173ce14137a50095476e653c"
}
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/rustconf-code`
OpenWeatherConfig {
api_key: "1b13e6aa173ce14137a50095476e653c"
}
use std::path::PathBuf;
use structopt::StructOpt;
/// A command-line interface to the openweathermap.org API.
#[derive(Debug, StructOpt)]
struct Opt {
/// Config filename; a JSON file with an `api_key` field.
#[structopt(short, long, parse(from_os_str))]
config: PathBuf,
}
fn main() {
let opt = Opt::from_args();
// ...
}
$ ./target/debug/rustconf-code --help
rustconf-code 0.1.0
A command-line interface to the openweathermap.org API
USAGE:
rustconf-code --config <config>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-c, --config <config> Config filename; a JSON file with
an `api_key` field
$ ./target/debug/rustconf-code --confgi my-custom-file.json
error: Found argument '--confgi' which wasn't expected, or isn't
valid in this context
Did you mean --config?
USAGE:
rustconf-code --config <config>
For more information try --help
fn main() -> eyre::Result<()> {
let opt = Opt::from_args();
let config_json = File::open(&opt.config)?;
let config: OpenWeatherConfig =
serde_json::from_reader(&config_json)?;
println!("Config: {:#?}", config);
Ok(())
}
fn main() -> eyre::Result<()> {
let opt = Opt::from_args();
let config_json =
match File::open(&opt.config) {
Ok(file) => file,
Err(err) => return Err(err.into()),
};
let config: OpenWeatherConfig =
match serde_json::from_reader(&config_json) {
Ok(config) => config,
Err(err) => return Err(err.into()),
};
println!("Config: {:#?}", config);
Ok(())
}
use eyre::WrapErr;
fn main() -> eyre::Result<()> {
let opt = Opt::from_args();
let config_json =
File::open(&opt.config).wrap_err_with(|| {
format!(
"Failed to open config file {:?}",
opt.config
)
})?;
let config: OpenWeatherConfig =
serde_json::from_reader(&config_json)
.wrap_err("Failed to deserialize JSON")?;
println!("Config: {:#?}", config);
Ok(())
}
$ ./target/debug/rustconf-code --config nonexistent-file.json
Error: Failed to open config file "nonexistent-file.json"
Caused by:
No such file or directory (os error 2)
$ ./target/debug/rustconf-code --config invalid-file.json
Error: Failed to deserialize JSON
Caused by:
trailing comma at line 3 column 1
use reqwest::blocking::{Client, Response};
fn get_weather(
api_key: &str,
) -> Result<Response, reqwest::Error> {
let client = Client::new();
client
.get("https://api.openweathermap.org/data/2.5/weather")
.query(&[("q", "Waltham,MA,US"), ("appid", api_key)])
.send()
}
println!("Response: {:#?}", get_weather(&config.api_key)?);
$ cargo run
Response: Response {
url: "https://api.openweathermap.org/data/2.5/weather?q=...",
status: 200,
headers: {
"server": "openresty",
"date": "Sun, 19 Jul 2020 19:30:04 GMT",
"content-type": "application/json; charset=utf-8",
"content-length": "465",
"connection": "keep-alive",
"x-cache-key": "/data/2.5/weather?q=waltham%2cma%2cus",
},
}
let res = get_weather(&config.api_key)?;
let bytes = res.bytes()?;
println!("{}", String::from_utf8_lossy(&*bytes));
{"coord":{"lon":-71.24,"lat":42.38},"weather":[{"id":802,
"main":"Clouds","description":"scattered clouds","icon":
"03d"}],"base":"stations","main":{"temp":308.71,"feels_like":
307.42,"temp_min":307.59,"temp_max":309.82,"pressure":1010,
"humidity":37},"visibility":10000,"wind":{"speed":6.2, ...
fn main() -> eyre::Result<()> {
// ...
let config: OpenWeather = serde_json::from_reader(
&config_json,
)?;
// ...
}
impl OpenWeather {
fn get<Response: DeserializeOwned>(
&self,
endpoint: &str,
params: &[(&str, &str)],
) -> eyre::Result<Response> {
// I have discovered a truly marvelous implementation
// of this method, which this slide is too narrow
// to contain.
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct OneCall {
pub hourly: Vec<Hourly>,
pub daily: Vec<Daily>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Hourly {
pub dt: UnixUTC,
pub temp: f64,
pub feels_like: f64,
pub humidity: f64,
pub clouds: f64,
pub rain: Option<Rain>,
pub snow: Option<Snow>,
}
impl OpenWeather {
fn onecall(&self) -> eyre::Result<OneCall> {
self.get(
"onecall",
&[ ("exclude", "currently,minutely"),
("units", "imperial"), ],
)
}
}
fn main() -> eyre::Result<()> {
// ...
let onecall: OneCall = config
.onecall()
.wrap_err("Failed to deserialize hourly weather data")?;
}
#[derive(Debug, PartialEq)]
enum TempDifference {
MuchColder,
Colder,
Same,
Warmer,
MuchWarmer,
}
impl TempDifference {
fn from(from: f64, to: f64) -> Self {
let delta = to - from;
match delta {
d if d > 10.0 => TempDifference::MuchWarmer,
d if d > 5.0 => TempDifference::Warmer,
d if d < -10.0 => TempDifference::MuchColder,
d if d < -5.0 => TempDifference::Colder,
_ => TempDifference::Same,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_tempdiff() {
assert_eq!(
TempDifference::from(50.0, 69.0),
TempDifference::MuchWarmer
);
// And so on, but these slides are small...
}
}
$ cargo test
Finished test [unoptimized + debuginfo] target(s)
in 0.04s
Running target/debug/deps/rustconf_code-affd1f0e8
running 1 test
test test::test_tempdiff ... ok
test result: ok. 1 passed; 0 failed; 0 ignored;
0 measured; 0 filtered out
struct Stats {
min: f64,
max: f64,
avg: f64,
count: usize,
}
impl Default for Stats {
fn default() -> Self {
Self {
min: f64::INFINITY,
max: f64::NEG_INFINITY,
avg: 0.0,
count: 0,
}
}
}
impl Stats {
fn from(itr: impl Iterator<Item = f64>) -> Self {
let mut ret = Self::default();
let mut sum = 0.0;
// ...
}
}
impl Stats {
fn from(itr: impl Iterator<Item = f64>) -> Self {
// ...
for i in itr {
if i < ret.min {
ret.min = i;
} else if i > ret.max {
ret.max = i;
}
ret.count += 1;
sum += i;
}
// ...
}
}
impl Stats {
fn from(itr: impl Iterator<Item = f64>) -> Self {
// ...
ret.avg = sum / ret.count as f64;
ret
}
}
let yesterday =
Stats::from(historical.iter().map(|h| h.feels_like));
let today = Stats::from(
onecall.hourly.iter().map(|h| h.feels_like).take(24),
);
let diff = TempDifference::from(yesterday.avg, today.avg);
let today_is_warm = 60.0 <= today.avg && today.avg <= 80.0;
print!("Good morning! Today will be about {:.2}°F ", today.avg);
println!(
"({min} - {max}°F); that's {diff} {than} yesterday{end}",
min = today.min,
max = today.max,
diff = diff,
than = match diff {
TempDifference::Same => "as",
_ => "than",
},
end = if today_is_warm { " :)" } else { "." },
);
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
$ ./target/debug/rustconf-code
Good morning! Today will be about 85.16°F (76.42 - 94.96°F);
that's about the same as yesterday.
$ ./target/debug/rustconf-code | lp
request id is star-22 (0 file(s))
.clone()