[#14] BasicDate now relies on DateTime for its parsing
							parent
							
								
									5dd2b3a878
								
							
						
					
					
						commit
						dd1efaa34d
					
				| 
						 | 
					@ -174,6 +174,16 @@ dependencies = [
 | 
				
			||||||
 "winapi 0.3.9",
 | 
					 "winapi 0.3.9",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "chrono-tz"
 | 
				
			||||||
 | 
					version = "0.5.3"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "chrono",
 | 
				
			||||||
 | 
					 "parse-zoneinfo",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "cipher"
 | 
					name = "cipher"
 | 
				
			||||||
version = "0.2.5"
 | 
					version = "0.2.5"
 | 
				
			||||||
| 
						 | 
					@ -340,6 +350,7 @@ name = "fej"
 | 
				
			||||||
version = "0.0.1"
 | 
					version = "0.0.1"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "chrono",
 | 
					 "chrono",
 | 
				
			||||||
 | 
					 "chrono-tz",
 | 
				
			||||||
 "regex",
 | 
					 "regex",
 | 
				
			||||||
 "reqwest",
 | 
					 "reqwest",
 | 
				
			||||||
 "rocket",
 | 
					 "rocket",
 | 
				
			||||||
| 
						 | 
					@ -1010,6 +1021,15 @@ dependencies = [
 | 
				
			||||||
 "vcpkg",
 | 
					 "vcpkg",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "parse-zoneinfo"
 | 
				
			||||||
 | 
					version = "0.3.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "regex",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "pear"
 | 
					name = "pear"
 | 
				
			||||||
version = "0.1.4"
 | 
					version = "0.1.4"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ edition = "2018"
 | 
				
			||||||
rocket = "0.4.7"
 | 
					rocket = "0.4.7"
 | 
				
			||||||
serde = "1.0.124"
 | 
					serde = "1.0.124"
 | 
				
			||||||
chrono = "0.4.19"
 | 
					chrono = "0.4.19"
 | 
				
			||||||
 | 
					chrono-tz = "0.5.3"
 | 
				
			||||||
regex = "1.4.5"
 | 
					regex = "1.4.5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.reqwest]
 | 
					[dependencies.reqwest]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					// I can probably do this way easier using an external crate, I should do that
 | 
				
			||||||
use rocket::http::Status;
 | 
					use rocket::http::Status;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub enum FejError {
 | 
					pub enum FejError {
 | 
				
			||||||
| 
						 | 
					@ -20,3 +21,9 @@ impl From<reqwest::Error> for FejError {
 | 
				
			||||||
        FejError::FailedRequest
 | 
					        FejError::FailedRequest
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<chrono::ParseError> for FejError {
 | 
				
			||||||
 | 
					    fn from(_: chrono::ParseError) -> FejError {
 | 
				
			||||||
 | 
					        FejError::InvalidArgument
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
use super::structs::{BasicDate, PickupTime, Street};
 | 
					use super::structs::{BasicDate, PickupTime, Street};
 | 
				
			||||||
use crate::errors::FejError;
 | 
					use crate::errors::FejError;
 | 
				
			||||||
 | 
					use chrono::DateTime;
 | 
				
			||||||
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
use reqwest::blocking as reqwest;
 | 
					use reqwest::blocking as reqwest;
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::convert::{From, TryFrom};
 | 
					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";
 | 
					const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn get_pickup_times(
 | 
					pub fn get_pickup_times(
 | 
				
			||||||
    street: Street,
 | 
					    street: &Street,
 | 
				
			||||||
    number: u32,
 | 
					    number: &u32,
 | 
				
			||||||
    start_date: BasicDate,
 | 
					    start_date: &DateTime<Tz>,
 | 
				
			||||||
    end_date: BasicDate,
 | 
					    end_date: &DateTime<Tz>,
 | 
				
			||||||
) -> Result<Vec<PickupTime>, FejError> {
 | 
					) -> Result<Vec<PickupTime>, FejError> {
 | 
				
			||||||
    let client = reqwest::Client::builder().cookie_store(true).build()?;
 | 
					    let client = reqwest::Client::builder().cookie_store(true).build()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,8 +33,8 @@ pub fn get_pickup_times(
 | 
				
			||||||
        .query(&[
 | 
					        .query(&[
 | 
				
			||||||
            ("_format", "json"),
 | 
					            ("_format", "json"),
 | 
				
			||||||
            ("type", ""),
 | 
					            ("type", ""),
 | 
				
			||||||
            ("start", &start_date.epoch().to_string()),
 | 
					            ("start", &start_date.timestamp().to_string()),
 | 
				
			||||||
            ("end", &end_date.epoch().to_string()),
 | 
					            ("end", &end_date.timestamp().to_string()),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
        .send()?;
 | 
					        .send()?;
 | 
				
			||||||
    let data: Vec<HashMap<String, String>> = response.json()?;
 | 
					    let data: Vec<HashMap<String, String>> = response.json()?;
 | 
				
			||||||
| 
						 | 
					@ -41,8 +43,9 @@ pub fn get_pickup_times(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for map in data.iter() {
 | 
					    for map in data.iter() {
 | 
				
			||||||
        output.push(PickupTime::new(
 | 
					        output.push(PickupTime::new(
 | 
				
			||||||
            // TODO should I check here if the parsing worked?
 | 
					            // TODO it's really not logical here that this would return an
 | 
				
			||||||
            BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(),
 | 
					            // "InvalidArgument" error, it should just skip the item
 | 
				
			||||||
 | 
					            BasicDate::try_from(map.get("date").unwrap().as_str())?,
 | 
				
			||||||
            map.get("label").unwrap().to_string(),
 | 
					            map.get("label").unwrap().to_string(),
 | 
				
			||||||
        ))
 | 
					        ))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +1,16 @@
 | 
				
			||||||
use chrono::{FixedOffset, TimeZone};
 | 
					use crate::errors::FejError;
 | 
				
			||||||
use regex::Regex;
 | 
					use chrono::{DateTime, NaiveDate, TimeZone};
 | 
				
			||||||
 | 
					use chrono_tz::Europe::Brussels;
 | 
				
			||||||
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
use rocket::http::RawStr;
 | 
					use rocket::http::RawStr;
 | 
				
			||||||
use rocket::request::FromFormValue;
 | 
					use rocket::request::FromFormValue;
 | 
				
			||||||
use serde::ser::Serializer;
 | 
					use serde::ser::Serializer;
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
use std::convert::TryFrom;
 | 
					use std::convert::TryFrom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Represents a very simple Timezoneless date. Considering the timezone will
 | 
					/// This class is a simple wrapper around chrono's DateTime. Its sole purpose
 | 
				
			||||||
/// always be CEST (aka Belgium's timezone), this is good enough. I use this
 | 
					/// is to avoid error E0117.
 | 
				
			||||||
/// instead of a NaiveDate to avoid E0117.
 | 
					pub struct BasicDate(pub DateTime<Tz>);
 | 
				
			||||||
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 allows us to use BasicDate as a query parameter in our routes.
 | 
					/// This allows us to use BasicDate as a query parameter in our routes.
 | 
				
			||||||
impl<'v> FromFormValue<'v> for BasicDate {
 | 
					impl<'v> FromFormValue<'v> for BasicDate {
 | 
				
			||||||
| 
						 | 
					@ -41,30 +24,26 @@ 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.
 | 
					/// This is used to serialize BasicDate.
 | 
				
			||||||
impl TryFrom<&str> for BasicDate {
 | 
					impl TryFrom<&str> for BasicDate {
 | 
				
			||||||
    type Error = ();
 | 
					    type Error = FejError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
 | 
					    fn try_from(s: &str) -> Result<BasicDate, Self::Error> {
 | 
				
			||||||
        let re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})$").unwrap();
 | 
					        let naive_date = NaiveDate::parse_from_str(s, "%Y-%m-%d")?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match re.captures(s) {
 | 
					        Ok(BasicDate(
 | 
				
			||||||
            None => Err(()),
 | 
					            Brussels
 | 
				
			||||||
            Some(caps) => Ok(BasicDate {
 | 
					                .from_local_datetime(&naive_date.and_hms(0, 0, 0))
 | 
				
			||||||
                // TODO change this to ? operator if possible
 | 
					                .single()
 | 
				
			||||||
                year: caps.get(1).unwrap().as_str().parse().unwrap(),
 | 
					                .ok_or(FejError::InvalidArgument)?,
 | 
				
			||||||
                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"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Serialize for BasicDate {
 | 
					impl Serialize for BasicDate {
 | 
				
			||||||
| 
						 | 
					@ -72,6 +51,6 @@ impl Serialize for BasicDate {
 | 
				
			||||||
    where
 | 
					    where
 | 
				
			||||||
        S: Serializer,
 | 
					        S: Serializer,
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        serializer.serialize_str(&self.to_string())
 | 
					        serializer.serialize_str(&String::from(self))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,8 +10,8 @@ pub struct Street {
 | 
				
			||||||
    pub city: String,
 | 
					    pub city: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<Street> for String {
 | 
					impl From<&Street> for String {
 | 
				
			||||||
    fn from(street: Street) -> String {
 | 
					    fn from(street: &Street) -> String {
 | 
				
			||||||
        format!("{} ({})", street.name, street.city)
 | 
					        format!("{} ({})", street.name, street.city)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,9 @@ pub fn route_get_pickup_times(
 | 
				
			||||||
    end_date: BasicDate,
 | 
					    end_date: BasicDate,
 | 
				
			||||||
) -> Result<Json<Vec<PickupTime>>, Status> {
 | 
					) -> Result<Json<Vec<PickupTime>>, Status> {
 | 
				
			||||||
    Ok(Json(get_pickup_times(
 | 
					    Ok(Json(get_pickup_times(
 | 
				
			||||||
        street, number, start_date, end_date,
 | 
					        &street,
 | 
				
			||||||
 | 
					        &number,
 | 
				
			||||||
 | 
					        &start_date.0,
 | 
				
			||||||
 | 
					        &end_date.0,
 | 
				
			||||||
    )?))
 | 
					    )?))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@
 | 
				
			||||||
#[macro_use]
 | 
					#[macro_use]
 | 
				
			||||||
extern crate rocket;
 | 
					extern crate rocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern crate chrono_tz;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Route modules
 | 
					// Route modules
 | 
				
			||||||
mod catchers;
 | 
					mod catchers;
 | 
				
			||||||
mod errors;
 | 
					mod errors;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue