diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index ef745d0..fddd7e4 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,4 +1,6 @@ mod pickup_times; mod search; -pub use pickup_times::{get_pickup_times, BasicDate, PickupTime}; -pub use search::{search_streets, Street}; +pub mod structs; + +pub use pickup_times::get_pickup_times; +pub use search::search_streets; diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 7e8d3fb..3727117 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,106 +1,12 @@ -use super::search::Street; +use super::structs::{BasicDate, PickupTime, Street}; use crate::errors::FejError; -use chrono::{FixedOffset, TimeZone}; -use regex::Regex; use reqwest::blocking as reqwest; -use rocket::http::RawStr; -use rocket::request::FromFormValue; -use serde::ser::{SerializeStruct, Serializer}; -use serde::Serialize; use std::collections::HashMap; use std::convert::{From, TryFrom}; 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"; -/// 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, -} - -/// This allows us to use BasicDate as a query parameter in our routes. -impl<'v> FromFormValue<'v> for BasicDate { - type Error = &'v RawStr; - - fn from_form_value(form_value: &'v RawStr) -> Result { - match BasicDate::try_from(form_value.as_str()) { - Err(_) => Err(form_value), - Ok(date) => Ok(date), - } - } -} - -/// 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 = (); - - fn try_from(s: &str) -> Result { - 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 Serialize for BasicDate { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl From for i64 { - fn from(date: BasicDate) -> i64 { - // Timezone of Brussels is UTC + 2 hours in the western hemisphere - FixedOffset::west(7_200) - .ymd(date.year as i32, date.month as u32, date.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() - } -} - -impl Serialize for PickupTime { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut s = serializer.serialize_struct("PickupTime", 2)?; - s.serialize_field("date", &self.date)?; - s.serialize_field("label", &self.label)?; - s.end() - } -} - -/// Represents a pickup time instance. All fields are a direct map of the -/// original API -pub struct PickupTime { - date: BasicDate, - label: String, -} - pub fn get_pickup_times( street: Street, number: u32, diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 5037bb4..8f5d8ff 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -1,71 +1,12 @@ +use super::structs::Street; use crate::errors::FejError; -use regex::Regex; use reqwest::blocking as reqwest; -use rocket::http::RawStr; -use rocket::request::FromFormValue; -use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::collections::HashMap; use std::convert::TryFrom; /// Endpoint for the search feature const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; -impl From for String { - fn from(street: Street) -> String { - format!("{} ({})", street.name, street.city) - } -} - -impl Serialize for Street { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut s = serializer.serialize_struct("Street", 2)?; - s.serialize_field("name", &self.name)?; - s.serialize_field("city", &self.city)?; - s.end() - } -} - -impl TryFrom for Street { - type Error = (); - - fn try_from(value: String) -> Result { - if let Some(index) = value.find('(') { - Ok(Street { - name: (value[0..index - 1].trim()).to_string(), - city: (value[index + 1..value.len() - 1].trim()).to_string(), - }) - } else { - Err(()) - } - } -} - -impl<'v> FromFormValue<'v> for Street { - type Error = &'v RawStr; - - fn from_form_value(form_value: &'v RawStr) -> Result { - // This regex is pretty loose tbh, but not sure how I can make it more - // strict right now - let re = Regex::new(r"^(.+) \((.+)\)$").unwrap(); - match re.captures(&form_value.url_decode_lossy()) { - None => Err(form_value), - Some(caps) => Ok(Street { - name: String::from(caps.get(1).unwrap().as_str()), - city: String::from(caps.get(2).unwrap().as_str()), - }), - } - } -} - -/// Represents a street -pub struct Street { - pub name: String, - pub city: String, -} - /// Searches the Ivago API for streets in the given city /// /// # Arguments diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs new file mode 100644 index 0000000..8669954 --- /dev/null +++ b/src/ivago/controller/structs/basic_date.rs @@ -0,0 +1,76 @@ +use chrono::{FixedOffset, TimeZone}; +use regex::Regex; +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, +} + +/// This allows us to use BasicDate as a query parameter in our routes. +impl<'v> FromFormValue<'v> for BasicDate { + type Error = &'v RawStr; + + fn from_form_value(form_value: &'v RawStr) -> Result { + match BasicDate::try_from(form_value.as_str()) { + Err(_) => Err(form_value), + Ok(date) => Ok(date), + } + } +} + +/// 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 = (); + + fn try_from(s: &str) -> Result { + 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 Serialize for BasicDate { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl From for i64 { + fn from(date: BasicDate) -> i64 { + // Timezone of Brussels is UTC + 2 hours in the western hemisphere + FixedOffset::west(7_200) + .ymd(date.year as i32, date.month as u32, date.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() + } +} diff --git a/src/ivago/controller/structs/mod.rs b/src/ivago/controller/structs/mod.rs new file mode 100644 index 0000000..ed6cf3c --- /dev/null +++ b/src/ivago/controller/structs/mod.rs @@ -0,0 +1,7 @@ +mod basic_date; +mod pickup_time; +mod street; + +pub use basic_date::BasicDate; +pub use pickup_time::PickupTime; +pub use street::Street; diff --git a/src/ivago/controller/structs/pickup_time.rs b/src/ivago/controller/structs/pickup_time.rs new file mode 100644 index 0000000..78d7e8e --- /dev/null +++ b/src/ivago/controller/structs/pickup_time.rs @@ -0,0 +1,21 @@ +use super::BasicDate; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +impl Serialize for PickupTime { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("PickupTime", 2)?; + s.serialize_field("date", &self.date)?; + s.serialize_field("label", &self.label)?; + s.end() + } +} + +/// Represents a pickup time instance. All fields are a direct map of the +/// original API +pub struct PickupTime { + pub date: BasicDate, + pub label: String, +} diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/structs/street.rs new file mode 100644 index 0000000..257d694 --- /dev/null +++ b/src/ivago/controller/structs/street.rs @@ -0,0 +1,61 @@ +use regex::Regex; +use rocket::http::RawStr; +use rocket::request::FromFormValue; +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::convert::TryFrom; + +/// Represents a street +pub struct Street { + pub name: String, + pub city: String, +} + +impl From for String { + fn from(street: Street) -> String { + format!("{} ({})", street.name, street.city) + } +} + +impl Serialize for Street { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("Street", 2)?; + s.serialize_field("name", &self.name)?; + s.serialize_field("city", &self.city)?; + s.end() + } +} + +impl TryFrom for Street { + type Error = (); + + fn try_from(value: String) -> Result { + if let Some(index) = value.find('(') { + Ok(Street { + name: (value[0..index - 1].trim()).to_string(), + city: (value[index + 1..value.len() - 1].trim()).to_string(), + }) + } else { + Err(()) + } + } +} + +impl<'v> FromFormValue<'v> for Street { + type Error = &'v RawStr; + + fn from_form_value(form_value: &'v RawStr) -> Result { + // This regex is pretty loose tbh, but not sure how I can make it more + // strict right now + let re = Regex::new(r"^(.+) \((.+)\)$").unwrap(); + match re.captures(&form_value.url_decode_lossy()) { + None => Err(form_value), + Some(caps) => Ok(Street { + name: String::from(caps.get(1).unwrap().as_str()), + city: String::from(caps.get(2).unwrap().as_str()), + }), + } + } +} diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 541b27a..cc916d0 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -2,6 +2,8 @@ mod controller; #[cfg(test)] mod tests; +use controller::structs::{BasicDate, PickupTime, Street}; +use controller::{get_pickup_times, search_streets}; use rocket::http::Status; use rocket_contrib::json::Json; @@ -10,18 +12,18 @@ pub fn routes() -> Vec { } #[get("/search?")] -pub fn route_search_streets(street: String) -> Result>, Status> { - Ok(Json(controller::search_streets(&street)?)) +pub fn route_search_streets(street: String) -> Result>, Status> { + Ok(Json(search_streets(&street)?)) } #[get("/?&&&")] pub fn route_get_pickup_times( - street: controller::Street, + street: Street, number: u32, - start_date: controller::BasicDate, - end_date: controller::BasicDate, -) -> Result>, Status> { - Ok(Json(controller::get_pickup_times( + start_date: BasicDate, + end_date: BasicDate, +) -> Result>, Status> { + Ok(Json(get_pickup_times( street, number, start_date, end_date, )?)) }