conditions/weather/
weather_api.rs1use std::convert::From;
2
3use serde::Deserialize;
4use thiserror::Error;
5
6use super::CurrentConditions;
7use crate::{config::Config, icons::TimeOfDay, location::Location};
8
9pub struct Client {
10 is_valid: bool,
11 query: Vec<(String, String)>,
12}
13
14impl Client {
15 pub fn new(config: &Config, location: &Location) -> Self {
16 let key = config.weatherapi_token.clone();
17 let is_valid = key.is_some();
18
19 Self {
20 is_valid,
21 query: vec![
22 ("key".to_string(), key.unwrap_or_default()),
23 ("q".to_string(), location.loc.clone()),
24 ],
25 }
26 }
27}
28
29impl crate::api::Fetchable<Response, CurrentConditions> for Client {
30 fn url(&self) -> &'static str {
31 "http://api.weatherapi.com/v1/current.json"
32 }
33
34 fn is_valid(&self) -> bool {
35 self.is_valid
36 }
37
38 fn query(&self) -> Option<&Vec<(String, String)>> {
39 Some(&self.query)
40 }
41}
42
43#[derive(Error, Debug, PartialEq)]
44pub enum FetchConditionsError {
45 #[error("unknown error fetching weather conditions")]
46 Unknown,
47}
48
49impl std::convert::From<ureq::Error> for FetchConditionsError {
50 fn from(_: ureq::Error) -> Self {
51 Self::Unknown
52 }
53}
54
55impl std::convert::From<std::io::Error> for FetchConditionsError {
56 fn from(_: std::io::Error) -> Self {
57 Self::Unknown
58 }
59}
60
61#[derive(Debug, Deserialize)]
74pub struct Response {
75 current: WeatherAPIResultCurrent,
76}
77
78#[derive(Debug, Deserialize)]
79struct WeatherAPIResultCurrent {
80 condition: WeatherAPIResultCondition,
81 temp_c: f32,
82 temp_f: f32,
83 is_day: u8,
84}
85
86#[derive(Debug, Deserialize)]
87struct WeatherAPIResultCondition {
88 code: i32,
89}
90
91impl From<Response> for CurrentConditions {
92 fn from(result: Response) -> Self {
93 let icon = TimeOfDay::from(result.current.is_day)
94 .icon(&super::Source::WeatherAPI, result.current.condition.code);
95
96 Self {
97 temp_c: result.current.temp_c,
98 temp_f: result.current.temp_f,
99 icon,
100 }
101 }
102}
103
104#[cfg(test)]
105mod test {
106 use super::*;
107
108 #[test]
109 fn it_creates_client_with_query() {
110 let config = Config {
111 weatherapi_token: Some("token123".to_string()),
112 ..Default::default()
113 };
114 let location = Location {
115 loc: "loc_string".to_string(),
116 ..Default::default()
117 };
118 let client = Client::new(&config, &location);
119
120 assert_eq!(
121 client.query,
122 vec![
123 ("key".to_string(), "token123".to_string()),
124 ("q".to_string(), "loc_string".to_string())
125 ]
126 );
127 }
128
129 #[test]
130 fn test_weatherapi_from() {
131 let response = Response {
132 current: WeatherAPIResultCurrent {
133 condition: WeatherAPIResultCondition { code: 1087 },
134 temp_c: 10.0,
135 temp_f: 50.0,
136 is_day: 0,
137 },
138 };
139 let conditions = CurrentConditions::from(response);
140 assert!((conditions.temp_c - 10.0).abs() < f32::EPSILON);
141 assert!((conditions.temp_f - 50.0).abs() < f32::EPSILON);
142 assert_eq!(conditions.icon, "".to_string());
143 }
144}