#![doc(html_root_url = "https://jgrillo.github.io/forecast-rs/")]
#[macro_use]
extern crate serde_derive;
use std::vec::Vec;
use std::borrow::Borrow;
use std::option::Option;
use serde::de::{Deserialize, Deserializer, IntoDeserializer};
use serde::ser::{Serialize, Serializer};
use itertools::join;
use reqwest::{Url, Result as ApiResult, Client, Response};
static FORECAST_URL: &'static str = "https://api.darksky.net/forecast";
static EXCLUDE: &'static str = "exclude";
static EXTEND: &'static str = "extend";
static LANG: &'static str = "lang";
static UNITS: &'static str = "units";
#[derive(Debug)]
pub struct ApiClient<'a> {
client: &'a Client
}
impl<'a> ApiClient<'a> {
pub fn new(client: &'a Client) -> ApiClient<'a> {
ApiClient { client }
}
pub async fn get_forecast<'b, T>(&self, request: T) -> ApiResult<Response>
where T : Borrow<ForecastRequest<'b>> + Sized {
self.client.get(request.borrow().url.clone())
.send().await
}
pub async fn get_time_machine<'b, T>(&self, request: T) -> ApiResult<Response>
where T : Borrow<TimeMachineRequest<'b>> + Sized {
self.client.get(request.borrow().url.clone())
.send().await
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ForecastRequest<'a> {
api_key: &'a str,
latitude: f64,
longitude: f64,
url: Url,
exclude: Vec<ExcludeBlock>,
extend: Option<ExtendBy>,
lang: Option<Lang>,
units: Option<Units>
}
impl<'a> ForecastRequest<'a> {
pub fn new(
api_key: &'a str,
latitude: f64,
longitude: f64,
url: Url,
exclude: Vec<ExcludeBlock>,
extend: Option<ExtendBy>,
lang: Option<Lang>,
units: Option<Units>
) -> ForecastRequest<'a> {
ForecastRequest {
api_key,
latitude,
longitude,
url,
exclude,
extend,
lang,
units
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ForecastRequestBuilder<'a> {
api_key: &'a str,
latitude: f64,
longitude: f64,
exclude: Vec<ExcludeBlock>,
extend: Option<ExtendBy>,
lang: Option<Lang>,
units: Option<Units>
}
impl<'a> ForecastRequestBuilder<'a> {
pub fn new(api_key: &'a str, latitude: f64, longitude: f64) -> ForecastRequestBuilder {
ForecastRequestBuilder {
api_key,
latitude,
longitude,
exclude: Vec::new(),
extend: None,
lang: None,
units: None
}
}
pub fn exclude_block(mut self, exclude_block: ExcludeBlock) -> ForecastRequestBuilder<'a> {
self.exclude.push(exclude_block);
self
}
pub fn exclude_blocks(mut self, exclude_blocks: &mut Vec<ExcludeBlock>)
-> ForecastRequestBuilder<'a> {
self.exclude.append(exclude_blocks);
self
}
pub fn extend(mut self, extend: ExtendBy) -> ForecastRequestBuilder<'a> {
self.extend = Some(extend);
self
}
pub fn lang(mut self, lang: Lang) -> ForecastRequestBuilder<'a> {
self.lang = Some(lang);
self
}
pub fn units(mut self, units: Units) -> ForecastRequestBuilder<'a> {
self.units = Some(units);
self
}
pub fn build(self) -> ForecastRequest<'a> {
ForecastRequest::new(
self.api_key,
self.latitude,
self.longitude,
self.build_url(),
self.exclude,
self.extend,
self.lang,
self.units
)
}
fn build_url(&self) -> Url {
let url_string = format!(
"{base}/{key}/{lat:.16},{long:.16}",
base = FORECAST_URL,
key = &self.api_key,
lat = &self.latitude,
long = &self.longitude
);
let mut url = Url::parse(&url_string).unwrap();
{
let mut query_pairs = url.query_pairs_mut();
if !&self.exclude.is_empty() {
let excludes = join(
&self.exclude
.iter()
.map(|e| {
let json = serde_json::to_string(e).unwrap();
json.trim_matches('"').to_string()
})
.collect::<Vec<String>>(),
","
);
query_pairs.append_pair(EXCLUDE, &excludes);
}
if let &Some(ref extend) = &self.extend {
query_pairs.append_pair(
EXTEND,
serde_json::to_string(&extend).unwrap().trim_matches('"')
);
}
if let &Some(ref lang) = &self.lang {
query_pairs.append_pair(
LANG,
serde_json::to_string(&lang).unwrap().trim_matches('"')
);
}
if let &Some(ref units) = &self.units {
query_pairs.append_pair(
UNITS,
serde_json::to_string(&units).unwrap().trim_matches('"')
);
}
};
url
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TimeMachineRequest<'a> {
api_key: &'a str,
latitude: f64,
longitude: f64,
time: u64,
url: Url,
exclude: Vec<ExcludeBlock>,
lang: Option<Lang>,
units: Option<Units>
}
impl<'a> TimeMachineRequest<'a> {
pub fn new(
api_key: &'a str,
latitude: f64,
longitude: f64,
time: u64,
url: Url,
exclude: Vec<ExcludeBlock>,
lang: Option<Lang>,
units: Option<Units>
) -> TimeMachineRequest<'a> {
TimeMachineRequest {
api_key,
latitude,
longitude,
time,
url,
exclude,
lang,
units
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TimeMachineRequestBuilder<'a> {
api_key: &'a str,
latitude: f64,
longitude: f64,
time: u64,
exclude: Vec<ExcludeBlock>,
lang: Option<Lang>,
units: Option<Units>
}
impl<'a> TimeMachineRequestBuilder<'a> {
pub fn new(
api_key: &'a str,
latitude: f64,
longitude: f64,
time: u64
) -> TimeMachineRequestBuilder {
TimeMachineRequestBuilder {
api_key,
latitude,
longitude,
time,
exclude: Vec::new(),
lang: None,
units: None
}
}
pub fn exclude_block(mut self, exclude_block: ExcludeBlock) -> TimeMachineRequestBuilder<'a> {
self.exclude.push(exclude_block);
self
}
pub fn exclude_blocks(
mut self,
exclude_blocks: &mut Vec<ExcludeBlock>
) -> TimeMachineRequestBuilder<'a> {
self.exclude.append(exclude_blocks);
self
}
pub fn lang(mut self, lang: Lang) -> TimeMachineRequestBuilder<'a> {
self.lang = Some(lang);
self
}
pub fn units(mut self, units: Units) -> TimeMachineRequestBuilder<'a> {
self.units = Some(units);
self
}
pub fn build(self) -> TimeMachineRequest<'a> {
TimeMachineRequest::new(
self.api_key,
self.latitude,
self.longitude,
self.time,
self.build_url(),
self.exclude,
self.lang,
self.units
)
}
fn build_url(&self) -> Url {
let url_string = format!(
"{base}/{key}/{lat:.16},{long:.16},{time}",
base = FORECAST_URL,
key = self.api_key,
lat = self.latitude,
long = self.longitude,
time = self.time
);
let mut url = Url::parse(&url_string).unwrap();
{
let mut query_pairs = url.query_pairs_mut();
if !self.exclude.is_empty() {
let excludes = join(
&self.exclude
.iter()
.map(|e| {
let json = serde_json::to_string(e).unwrap();
json.trim_matches('"').to_string()
})
.collect::<Vec<String>>(),
",",
);
query_pairs.append_pair(EXCLUDE, &excludes);
}
if let &Some(ref lang) = &self.lang {
query_pairs.append_pair(
LANG,
serde_json::to_string(&lang).unwrap().trim_matches('"')
);
}
if let &Some(ref units) = &self.units {
query_pairs.append_pair(
UNITS,
serde_json::to_string(&units).unwrap().trim_matches('"')
);
}
}
url
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum Icon {
#[serde(rename = "clear-day")]
ClearDay,
#[serde(rename = "clear-night")]
ClearNight,
#[serde(rename = "rain")]
Rain,
#[serde(rename = "snow")]
Snow,
#[serde(rename = "sleet")]
Sleet,
#[serde(rename = "wind")]
Wind,
#[serde(rename = "fog")]
Fog,
#[serde(rename = "cloudy")]
Cloudy,
#[serde(rename = "partly-cloudy-day")]
PartlyCloudyDay,
#[serde(rename = "partly-cloudy-night")]
PartlyCloudyNight,
#[serde(rename = "hail")]
Hail,
#[serde(rename = "thunderstorm")]
Thunderstorm,
#[serde(rename = "tornado")]
Tornado
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum PrecipType {
#[serde(rename = "rain")]
Rain,
#[serde(rename = "snow")]
Snow,
#[serde(rename = "sleet")]
Sleet
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum ExcludeBlock {
#[serde(rename = "currently")]
Currently,
#[serde(rename = "minutely")]
Minutely,
#[serde(rename = "hourly")]
Hourly,
#[serde(rename = "daily")]
Daily,
#[serde(rename = "alerts")]
Alerts,
#[serde(rename = "flags")]
Flags
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum ExtendBy {
#[serde(rename = "hourly")]
Hourly
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(remote = "Lang")]
pub enum Lang {
#[serde(rename = "ar")]
Arabic,
#[serde(rename = "az")]
Azerbaijani,
#[serde(rename = "be")]
Belarusian,
#[serde(rename = "bg")]
Bulgarian,
#[serde(rename = "bs")]
Bosnian,
#[serde(rename = "ca")]
Catalan,
#[serde(rename = "cz")]
Czech,
#[serde(rename = "da")]
Danish,
#[serde(rename = "de")]
German,
#[serde(rename = "el")]
Greek,
#[serde(rename = "en")]
English,
#[serde(rename = "es")]
Spanish,
#[serde(rename = "et")]
Estonian,
#[serde(rename = "fi")]
Finnish,
#[serde(rename = "fr")]
French,
#[serde(rename = "hr")]
Croatian,
#[serde(rename = "hu")]
Hungarian,
#[serde(rename = "id")]
Indonesian,
#[serde(rename = "is")]
Icelandic,
#[serde(rename = "it")]
Italian,
#[serde(rename = "ja")]
Japanese,
#[serde(rename = "ka")]
Georgian,
#[serde(rename = "ko")]
Korean,
#[serde(rename = "kw")]
Cornish,
#[serde(rename = "nb")]
NorwegianBokmal,
#[serde(rename = "nl")]
Dutch,
#[serde(rename = "pl")]
Polish,
#[serde(rename = "pt")]
Portuguese,
#[serde(rename = "ro")]
Romanian,
#[serde(rename = "ru")]
Russian,
#[serde(rename = "sk")]
Slovak,
#[serde(rename = "sl")]
Slovenian,
#[serde(rename = "sr")]
Serbian,
#[serde(rename = "sv")]
Swedish,
#[serde(rename = "tet")]
Tetum,
#[serde(rename = "tr")]
Turkish,
#[serde(rename = "uk")]
Ukranian,
#[serde(rename = "x-pig-latin")]
IgpayAtinlay,
#[serde(rename = "zh")]
SimplifiedChinese,
#[serde(rename = "zh-tw")]
TraditionalChinese
}
impl <'de> Deserialize<'de> for Lang {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let value = String::deserialize(deserializer)?;
if value == "no" {
Ok(Lang::NorwegianBokmal)
} else {
Lang::deserialize(value.into_deserializer())
}
}
}
impl Serialize for Lang {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
Lang::serialize(&self, serializer)
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum Units {
#[serde(rename = "auto")]
Auto,
#[serde(rename = "ca")]
CA,
#[serde(rename = "uk2")]
UK,
#[serde(rename = "us")]
Imperial,
#[serde(rename = "si")]
SI
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub enum Severity {
#[serde(rename = "advisory")]
Advisory,
#[serde(rename = "watch")]
Watch,
#[serde(rename = "warning")]
Warning
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct DataPoint {
#[serde(rename = "apparentTemperature")]
pub apparent_temperature: Option<f64>,
#[serde(rename = "apparentTemperatureHigh")]
pub apparent_temperature_high: Option<f64>,
#[serde(rename = "apparentTemperatureHighTime")]
pub apparent_temperature_high_time: Option<u64>,
#[serde(rename = "apparentTemperatureLow")]
pub apparent_temperature_low: Option<f64>,
#[serde(rename = "apparentTemperatureLowTime")]
pub apparent_temperature_low_time: Option<u64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "apparentTemperatureMax")]
pub apparent_temperature_max: Option<f64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "apparentTemperatureMaxTime")]
pub apparent_temperature_max_time: Option<u64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "apparentTemperatureMin")]
pub apparent_temperature_min: Option<f64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "apparentTemperatureMinTime")]
pub apparent_temperature_min_time: Option<u64>,
#[serde(rename = "cloudCover")]
pub cloud_cover: Option<f64>,
#[serde(rename = "dewPoint")]
pub dew_point: Option<f64>,
pub humidity: Option<f64>,
pub icon: Option<Icon>,
#[serde(rename = "moonPhase")]
pub moon_phase: Option<f64>,
#[serde(rename = "nearestStormBearing")]
pub nearest_storm_bearing: Option<f64>,
#[serde(rename = "nearestStormDistance")]
pub nearest_storm_distance: Option<f64>,
pub ozone: Option<f64>,
#[serde(rename = "precipAccumulation")]
pub precip_accumulation: Option<f64>,
#[serde(rename = "precipIntensity")]
pub precip_intensity: Option<f64>,
#[serde(rename = "precipIntensityMax")]
pub precip_intensity_max: Option<f64>,
#[serde(rename = "precipIntensityMaxTime")]
pub precip_intensity_max_time: Option<u64>,
#[serde(rename = "precipProbability")]
pub precip_probability: Option<f64>,
#[serde(rename = "precipType")]
pub precip_type: Option<PrecipType>,
pub pressure: Option<f64>,
pub summary: Option<String>,
#[serde(rename = "sunriseTime")]
pub sunrise_time: Option<u64>,
#[serde(rename = "sunsetTime")]
pub sunset_time: Option<u64>,
pub temperature: Option<f64>,
#[serde(rename = "temperatureHigh")]
pub temperature_high: Option<f64>,
#[serde(rename = "temperatureHighTime")]
pub temperature_high_time: Option<u64>,
#[serde(rename = "temperatureLow")]
pub temperature_low: Option<f64>,
#[serde(rename = "temperatureLowTime")]
pub temperature_low_time: Option<u64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "temperatureMax")]
pub temperature_max: Option<f64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "temperatureMaxTime")]
pub temperature_max_time: Option<u64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "temperatureMin")]
pub temperature_min: Option<f64>,
#[deprecated(since = "1.0.0")]
#[serde(rename = "temperatureMinTime")]
pub temperature_min_time: Option<u64>,
pub time: u64,
#[serde(rename = "uvIndex")]
pub uv_index: Option<f64>,
#[serde(rename = "uvIndexTime")]
pub uv_index_time: Option<u64>,
pub visibility: Option<f64>,
#[serde(rename = "windBearing")]
pub wind_bearing: Option<f64>,
#[serde(rename = "windGust")]
pub wind_gust: Option<f64>,
#[serde(rename = "windGustTime")]
pub wind_gust_time: Option<u64>,
#[serde(rename = "windSpeed")]
pub wind_speed: Option<f64>
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct DataBlock {
pub data: Vec<DataPoint>,
pub summary: Option<String>,
pub icon: Option<Icon>
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Alert {
pub description: String,
pub expires: u64,
pub regions: Vec<String>,
pub severity: Severity,
pub time: u64,
pub title: String,
pub uri: String
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct Flags {
#[serde(rename = "darksky-unavailable")]
pub darksky_unavailable: Option<String>,
pub sources: Vec<String>,
pub units: Units
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct ApiResponse {
pub latitude: f64,
pub longitude: f64,
pub timezone: String,
#[deprecated(since = "1.0.0")]
pub offset: i64,
pub currently: Option<DataPoint>,
pub minutely: Option<DataBlock>,
pub hourly: Option<DataBlock>,
pub daily: Option<DataBlock>,
pub alerts: Option<Vec<Alert>>,
pub flags: Option<Flags>
}
#[cfg(test)]
mod tests {
use super::{ForecastRequestBuilder, ForecastRequest, TimeMachineRequestBuilder,
TimeMachineRequest, ExcludeBlock, Units, Lang, ExtendBy, FORECAST_URL, EXCLUDE,
EXTEND, LANG, UNITS};
use reqwest::Url;
use serde_json;
use std::vec::Vec;
const LAT: f64 = 6.66;
const LONG: f64 = 66.6;
const TIME: u64 = 666;
static API_KEY: &'static str = "some_api_key";
#[test]
fn test_forecast_request_builder_defaults() {
let request = ForecastRequestBuilder::new(API_KEY, LAT, LONG).build();
let expected_url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16}?",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG
)).unwrap();
let expected = ForecastRequest::new(
API_KEY,
LAT,
LONG,
expected_url,
Vec::new(),
None,
None,
None
);
assert_eq!(expected.api_key, request.api_key);
assert_eq!(expected.latitude, request.latitude);
assert_eq!(expected.longitude, request.longitude);
assert_eq!(expected.exclude, request.exclude);
assert_eq!(expected.extend, request.extend);
assert_eq!(expected.lang, request.lang);
assert_eq!(expected.units, request.units);
assert_eq!(expected.url, request.url);
assert_eq!(expected, request);
}
#[test]
fn test_forecast_request_builder_simple() {
let mut blocks = vec![ExcludeBlock::Daily, ExcludeBlock::Alerts];
let request = ForecastRequestBuilder::new(API_KEY, LAT, LONG)
.exclude_block(ExcludeBlock::Hourly)
.exclude_blocks(&mut blocks)
.extend(ExtendBy::Hourly)
.lang(Lang::Arabic)
.units(Units::Imperial)
.build();
let expected_url = {
let mut url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16}",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG
)).unwrap();
url.query_pairs_mut()
.append_pair(EXCLUDE, "hourly,daily,alerts")
.append_pair(EXTEND, "hourly")
.append_pair(LANG, "ar")
.append_pair(UNITS, "us");
url
};
let expected = ForecastRequest::new(
API_KEY,
LAT,
LONG,
expected_url,
vec![
ExcludeBlock::Hourly,
ExcludeBlock::Daily,
ExcludeBlock::Alerts
],
Some(ExtendBy::Hourly),
Some(Lang::Arabic),
Some(Units::Imperial)
);
assert_eq!(expected, request);
}
#[test]
fn test_forecast_request_builder_complex() {
let mut builder = ForecastRequestBuilder::new(API_KEY, LAT, LONG);
let mut blocks = vec![ExcludeBlock::Daily, ExcludeBlock::Alerts];
builder = builder.exclude_block(ExcludeBlock::Hourly);
builder = builder.exclude_blocks(&mut blocks);
builder = builder.extend(ExtendBy::Hourly);
builder = builder.lang(Lang::Arabic);
builder = builder.units(Units::Imperial);
let expected_url = {
let mut url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16}",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG
)).unwrap();
url.query_pairs_mut()
.append_pair(EXCLUDE, "hourly,daily,alerts")
.append_pair(EXTEND, "hourly")
.append_pair(LANG, "ar")
.append_pair(UNITS, "us");
url
};
let expected = ForecastRequest::new(
API_KEY,
LAT,
LONG,
expected_url,
vec![
ExcludeBlock::Hourly,
ExcludeBlock::Daily,
ExcludeBlock::Alerts
],
Some(ExtendBy::Hourly),
Some(Lang::Arabic),
Some(Units::Imperial)
);
assert_eq!(expected, builder.build());
}
#[test]
fn test_time_machine_request_builder_defaults() {
let request = TimeMachineRequestBuilder::new(
API_KEY, LAT, LONG, TIME
).build();
let expected_url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16},{time}?",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG,
time = TIME
)).unwrap();
let expected = TimeMachineRequest::new(
API_KEY,
LAT,
LONG,
TIME,
expected_url,
Vec::new(),
None,
None
);
assert_eq!(expected.api_key, request.api_key);
assert_eq!(expected.latitude, request.latitude);
assert_eq!(expected.longitude, request.longitude);
assert_eq!(expected.time, request.time);
assert_eq!(expected.exclude, request.exclude);
assert_eq!(expected.lang, request.lang);
assert_eq!(expected.units, request.units);
assert_eq!(expected.url, request.url);
assert_eq!(expected, request);
}
#[test]
fn test_time_machine_request_builder_simple() {
let mut blocks = vec![ExcludeBlock::Daily, ExcludeBlock::Alerts];
let request = TimeMachineRequestBuilder::new(API_KEY, LAT, LONG, TIME)
.exclude_block(ExcludeBlock::Hourly)
.exclude_blocks(&mut blocks)
.lang(Lang::Arabic)
.units(Units::Imperial)
.build();
let expected_url = {
let mut url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16},{time}",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG,
time = TIME
)).unwrap();
url.query_pairs_mut()
.append_pair(EXCLUDE, "hourly,daily,alerts")
.append_pair(LANG, "ar")
.append_pair(UNITS, "us");
url
};
let expected = TimeMachineRequest::new(
API_KEY,
LAT,
LONG,
TIME,
expected_url,
vec![
ExcludeBlock::Hourly,
ExcludeBlock::Daily,
ExcludeBlock::Alerts
],
Some(Lang::Arabic),
Some(Units::Imperial)
);
assert_eq!(expected, request);
}
#[test]
fn test_time_machine_request_builder_complex() {
let mut builder = TimeMachineRequestBuilder::new(API_KEY, LAT, LONG, TIME);
let mut blocks = vec![ExcludeBlock::Daily, ExcludeBlock::Alerts];
builder = builder.exclude_block(ExcludeBlock::Hourly);
builder = builder.exclude_blocks(&mut blocks);
builder = builder.lang(Lang::Arabic);
builder = builder.units(Units::Imperial);
let expected_url = {
let mut url = Url::parse(&format!(
"{base}/{key}/{lat:.16},{long:.16},{time}",
base = FORECAST_URL,
key = API_KEY,
lat = LAT,
long = LONG,
time = TIME
)).unwrap();
url.query_pairs_mut()
.append_pair(EXCLUDE, "hourly,daily,alerts")
.append_pair(LANG, "ar")
.append_pair(UNITS, "us");
url
};
let expected = TimeMachineRequest::new(
API_KEY,
LAT,
LONG,
TIME,
expected_url,
vec![
ExcludeBlock::Hourly,
ExcludeBlock::Daily,
ExcludeBlock::Alerts
],
Some(Lang::Arabic),
Some(Units::Imperial)
);
assert_eq!(expected, builder.build());
}
#[test]
fn test_norwegian_lang_serde() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestStruct {
no: Lang,
nb: Lang,
en: Lang
}
let test_json = "{\"nb\":\"nb\",\"no\":\"no\",\"en\":\"en\"}";
let test_struct: TestStruct = serde_json::from_str(test_json).unwrap();
assert_eq!(test_struct.nb, Lang::NorwegianBokmal);
assert_eq!(test_struct.no, Lang::NorwegianBokmal);
assert_eq!(test_struct.en, Lang::English);
let test_struct_serialized = serde_json::to_string(&test_struct).unwrap();
let test_struct_deserialized: TestStruct = serde_json::from_str(
test_struct_serialized.as_str()
).unwrap();
assert_eq!(test_struct_deserialized.nb, Lang::NorwegianBokmal);
assert_eq!(test_struct_deserialized.no, Lang::NorwegianBokmal);
assert_eq!(test_struct_deserialized.en, Lang::English);
}
}