[#14] BasicDate now relies on DateTime for its parsing

This commit is contained in:
Jef Roosens 2021-04-08 10:11:15 +02:00
parent 5dd2b3a878
commit dd1efaa34d
Signed by: Jef Roosens
GPG key ID: B580B976584B5F30
8 changed files with 71 additions and 56 deletions

View file

@ -1,5 +1,7 @@
use super::structs::{BasicDate, PickupTime, Street};
use crate::errors::FejError;
use chrono::DateTime;
use chrono_tz::Tz;
use reqwest::blocking as reqwest;
use std::collections::HashMap;
use std::convert::{From, TryFrom};
@ -8,10 +10,10 @@ const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling";
const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups";
pub fn get_pickup_times(
street: Street,
number: u32,
start_date: BasicDate,
end_date: BasicDate,
street: &Street,
number: &u32,
start_date: &DateTime<Tz>,
end_date: &DateTime<Tz>,
) -> Result<Vec<PickupTime>, FejError> {
let client = reqwest::Client::builder().cookie_store(true).build()?;
@ -31,8 +33,8 @@ pub fn get_pickup_times(
.query(&[
("_format", "json"),
("type", ""),
("start", &start_date.epoch().to_string()),
("end", &end_date.epoch().to_string()),
("start", &start_date.timestamp().to_string()),
("end", &end_date.timestamp().to_string()),
])
.send()?;
let data: Vec<HashMap<String, String>> = response.json()?;
@ -41,8 +43,9 @@ pub fn get_pickup_times(
for map in data.iter() {
output.push(PickupTime::new(
// TODO should I check here if the parsing worked?
BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(),
// TODO it's really not logical here that this would return an
// "InvalidArgument" error, it should just skip the item
BasicDate::try_from(map.get("date").unwrap().as_str())?,
map.get("label").unwrap().to_string(),
))
}

View file

@ -1,33 +1,16 @@
use chrono::{FixedOffset, TimeZone};
use regex::Regex;
use crate::errors::FejError;
use chrono::{DateTime, NaiveDate, TimeZone};
use chrono_tz::Europe::Brussels;
use chrono_tz::Tz;
use rocket::http::RawStr;
use rocket::request::FromFormValue;
use serde::ser::Serializer;
use serde::Serialize;
use std::convert::TryFrom;
/// Represents a very simple Timezoneless date. Considering the timezone will
/// always be CEST (aka Belgium's timezone), this is good enough. I use this
/// instead of a NaiveDate to avoid E0117.
pub struct BasicDate {
year: u32,
month: u8,
day: u8,
}
impl BasicDate {
/// Return the seconds since epoch for this date
pub fn epoch(&self) -> i64 {
// Timezone of Brussels is UTC + 2 hours in the western hemisphere
FixedOffset::west(7_200)
.ymd(self.year as i32, self.month as u32, self.day as u32)
// For some reason, I couldn't get .timestamp() to work on a Date
// without a time component, even though the docs seemed to
// indicate this was possible
.and_hms(0, 0, 0)
.timestamp()
}
}
/// This class is a simple wrapper around chrono's DateTime. Its sole purpose
/// is to avoid error E0117.
pub struct BasicDate(pub DateTime<Tz>);
/// This allows us to use BasicDate as a query parameter in our routes.
impl<'v> FromFormValue<'v> for BasicDate {
@ -41,29 +24,25 @@ impl<'v> FromFormValue<'v> for BasicDate {
}
}
/// We need this when deserializing BasicDate.
impl ToString for BasicDate {
fn to_string(&self) -> String {
format!("{}-{}-{}", self.year, self.month, self.day)
/// This is used to serialize BasicDate.
impl TryFrom<&str> for BasicDate {
type Error = FejError;
fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
let naive_date = NaiveDate::parse_from_str(s, "%Y-%m-%d")?;
Ok(BasicDate(
Brussels
.from_local_datetime(&naive_date.and_hms(0, 0, 0))
.single()
.ok_or(FejError::InvalidArgument)?,
))
}
}
/// This is used to serialize BasicDate.
impl TryFrom<&str> for BasicDate {
type Error = ();
fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
let re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})$").unwrap();
match re.captures(s) {
None => Err(()),
Some(caps) => Ok(BasicDate {
// TODO change this to ? operator if possible
year: caps.get(1).unwrap().as_str().parse().unwrap(),
month: caps.get(2).unwrap().as_str().parse().unwrap(),
day: caps.get(3).unwrap().as_str().parse().unwrap(),
}),
}
impl From<&BasicDate> for String {
fn from(date: &BasicDate) -> String {
format!("{}", date.0.format("%Y-%m-%d"))
}
}
@ -72,6 +51,6 @@ impl Serialize for BasicDate {
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
serializer.serialize_str(&String::from(self))
}
}

View file

@ -10,8 +10,8 @@ pub struct Street {
pub city: String,
}
impl From<Street> for String {
fn from(street: Street) -> String {
impl From<&Street> for String {
fn from(street: &Street) -> String {
format!("{} ({})", street.name, street.city)
}
}

View file

@ -24,6 +24,9 @@ pub fn route_get_pickup_times(
end_date: BasicDate,
) -> Result<Json<Vec<PickupTime>>, Status> {
Ok(Json(get_pickup_times(
street, number, start_date, end_date,
&street,
&number,
&start_date.0,
&end_date.0,
)?))
}