parent
							
								
									27a61f8a9a
								
							
						
					
					
						commit
						c89841ad38
					
				|  | @ -56,6 +56,15 @@ dependencies = [ | ||||||
|  "opaque-debug", |  "opaque-debug", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "aho-corasick" | ||||||
|  | version = "0.7.15" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" | ||||||
|  | dependencies = [ | ||||||
|  |  "memchr", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "atty" | name = "atty" | ||||||
| version = "0.2.14" | version = "0.2.14" | ||||||
|  | @ -331,6 +340,7 @@ name = "fej" | ||||||
| version = "0.0.1" | version = "0.0.1" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "chrono", |  "chrono", | ||||||
|  |  "regex", | ||||||
|  "reqwest", |  "reqwest", | ||||||
|  "rocket", |  "rocket", | ||||||
|  "rocket_contrib", |  "rocket_contrib", | ||||||
|  | @ -1190,6 +1200,23 @@ dependencies = [ | ||||||
|  "bitflags", |  "bitflags", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "regex" | ||||||
|  | version = "1.4.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" | ||||||
|  | dependencies = [ | ||||||
|  |  "aho-corasick", | ||||||
|  |  "memchr", | ||||||
|  |  "regex-syntax", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "regex-syntax" | ||||||
|  | version = "0.6.23" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "remove_dir_all" | name = "remove_dir_all" | ||||||
| version = "0.5.3" | version = "0.5.3" | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  | regex = "1.4.5" | ||||||
| 
 | 
 | ||||||
| [dependencies.reqwest] | [dependencies.reqwest] | ||||||
| version = "0.11.2" | version = "0.11.2" | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										6
									
								
								Makefile
								
								
								
								
							|  | @ -23,7 +23,7 @@ push: | ||||||
| 
 | 
 | ||||||
| # Run
 | # Run
 | ||||||
| run: | run: | ||||||
| 	@ cargo run | 	@ RUST_BACKTRACE=1 cargo run | ||||||
| .PHONY: run | .PHONY: run | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -36,6 +36,10 @@ format: | ||||||
| 	@ cargo fmt | 	@ cargo fmt | ||||||
| .PHONY: format | .PHONY: format | ||||||
| 
 | 
 | ||||||
|  | lint: | ||||||
|  | 	@ cargo fmt -- --check | ||||||
|  | .PHONY: lint | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Documentation
 | # Documentation
 | ||||||
| docs: | docs: | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| mod pickup_times; | mod pickup_times; | ||||||
| mod search; | mod search; | ||||||
| pub use pickup_times::{get_pickup_times, PickupTime}; | pub use pickup_times::{get_pickup_times, BasicDate, PickupTime}; | ||||||
| pub use search::{search_streets, Street}; | pub use search::{search_streets, Street}; | ||||||
| 
 | 
 | ||||||
| ///// Return the known pickup times for the given street and/or city
 | ///// Return the known pickup times for the given street and/or city
 | ||||||
|  |  | ||||||
|  | @ -1,36 +1,83 @@ | ||||||
| use super::search::Street; | use super::search::Street; | ||||||
| use chrono::NaiveDate; | use regex::Regex; | ||||||
| use rocket::http::RawStr; | use rocket::http::RawStr; | ||||||
| use rocket::request::FromFormValue; | use rocket::request::FromFormValue; | ||||||
|  | use serde::ser::{Serialize, SerializeStruct, Serializer}; | ||||||
| use std::error::Error; | use std::error::Error; | ||||||
| 
 | 
 | ||||||
| const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; | const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; | ||||||
| 
 | 
 | ||||||
|  | /// Represents a very simple Timezoneless date. Considering the timezone will
 | ||||||
|  | /// always be CEST (aka Belgium's timezone), this is good enough.
 | ||||||
|  | pub struct BasicDate { | ||||||
|  |     year: u32, | ||||||
|  |     month: u8, | ||||||
|  |     day: u8, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'v> FromFormValue<'v> for BasicDate { | ||||||
|  |     type Error = &'v RawStr; | ||||||
|  | 
 | ||||||
|  |     fn from_form_value(form_value: &'v RawStr) -> Result<BasicDate, Self::Error> { | ||||||
|  |         // Beautiful how  this exact example is in the docs
 | ||||||
|  |         let re = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})$").unwrap(); | ||||||
|  |         match re.captures(form_value) { | ||||||
|  |             None => Err(form_value), | ||||||
|  |             // Here, we can assume these parses will work, because the regex
 | ||||||
|  |             // didn't fail
 | ||||||
|  |             Some(caps) => Ok(BasicDate { | ||||||
|  |                 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 ToString for BasicDate { | ||||||
|  |     fn to_string(&self) -> String { | ||||||
|  |         format!("{}-{}-{}", self.year, self.month, self.day) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Serialize for BasicDate { | ||||||
|  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||||
|  |     where | ||||||
|  |         S: Serializer, | ||||||
|  |     { | ||||||
|  |         serializer.serialize_str(&self.to_string()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Serialize for PickupTime { | ||||||
|  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||||
|  |     where | ||||||
|  |         S: Serializer, | ||||||
|  |     { | ||||||
|  |         let mut s = serializer.serialize_struct("PickupTime", 4)?; | ||||||
|  |         s.serialize_field("date", &self.date)?; | ||||||
|  |         s.serialize_field("label", &self.label)?; | ||||||
|  |         s.serialize_field("classes", &self.classes)?; | ||||||
|  |         s.serialize_field("url", &self.url)?; | ||||||
|  | 
 | ||||||
|  |         s.end() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Represents a pickup time instance. All fields are a direct map of the
 | /// Represents a pickup time instance. All fields are a direct map of the
 | ||||||
| /// original API
 | /// original API
 | ||||||
| pub struct PickupTime { | pub struct PickupTime { | ||||||
|     date: NaiveDate, |     date: BasicDate, | ||||||
|     label: String, |     label: String, | ||||||
|     classes: Vec<String>, |     classes: Vec<String>, | ||||||
|     url: String, |     url: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<'v> FromFormValue<'v> for NaiveDate { |  | ||||||
|     type Error = &'v RawStr; |  | ||||||
| 
 |  | ||||||
|     fn from_form_value(form_value: &'v RawStr) -> Result<NaiveDate, &'v RawStr> { |  | ||||||
|         match NaiveDate::parse_from_str(form_value, "%Y-%m-%d") { |  | ||||||
|             Ok(date) => Ok(date), |  | ||||||
|             Err(_) => Err(form_value), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn get_pickup_times( | pub fn get_pickup_times( | ||||||
|     street: Street, |     street: Street, | ||||||
|     number: u64, |     number: u32, | ||||||
|     start_date: NaiveDate, |     start_date: BasicDate, | ||||||
|     end_date: NaiveDate, |     end_date: BasicDate, | ||||||
| ) -> Result<Vec<PickupTime>, Box<dyn Error>> { | ) -> Result<Vec<PickupTime>, Box<dyn Error>> { | ||||||
|     Ok(Vec::new()) |     Ok(Vec::new()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,7 @@ | ||||||
|  | use regex::Regex; | ||||||
| use reqwest::blocking as reqwest; | use reqwest::blocking as reqwest; | ||||||
|  | use rocket::http::RawStr; | ||||||
|  | use rocket::request::FromFormValue; | ||||||
| use serde::ser::{Serialize, SerializeStruct, Serializer}; | use serde::ser::{Serialize, SerializeStruct, Serializer}; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::convert::TryFrom; | use std::convert::TryFrom; | ||||||
|  | @ -40,6 +43,23 @@ impl TryFrom<String> for Street { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl<'v> FromFormValue<'v> for Street { | ||||||
|  |     type Error = &'v RawStr; | ||||||
|  | 
 | ||||||
|  |     fn from_form_value(form_value: &'v RawStr) -> Result<Street, Self::Error> { | ||||||
|  |         // 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
 | /// Represents a street
 | ||||||
| pub struct Street { | pub struct Street { | ||||||
|     pub name: String, |     pub name: String, | ||||||
|  |  | ||||||
|  | @ -2,12 +2,11 @@ mod controller; | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests; | mod tests; | ||||||
| 
 | 
 | ||||||
| use chrono::NaiveDate; |  | ||||||
| use rocket::http::Status; | use rocket::http::Status; | ||||||
| use rocket_contrib::json::Json; | use rocket_contrib::json::Json; | ||||||
| 
 | 
 | ||||||
| pub fn routes() -> Vec<rocket::Route> { | pub fn routes() -> Vec<rocket::Route> { | ||||||
|     routes![route_search_streets,] |     routes![route_search_streets, route_get_pickup_times] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange
 | // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange
 | ||||||
|  | @ -15,7 +14,7 @@ pub fn routes() -> Vec<rocket::Route> { | ||||||
| pub fn route_search_streets(street: String) -> Result<Json<Vec<controller::Street>>, Status> { | pub fn route_search_streets(street: String) -> Result<Json<Vec<controller::Street>>, Status> { | ||||||
|     match controller::search_streets(&street) { |     match controller::search_streets(&street) { | ||||||
|         Ok(streets) => Ok(Json(streets)), |         Ok(streets) => Ok(Json(streets)), | ||||||
|         Err(err) => Err(Status::InternalServerError), |         Err(_) => Err(Status::InternalServerError), | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -23,8 +22,12 @@ pub fn route_search_streets(street: String) -> Result<Json<Vec<controller::Stree | ||||||
| pub fn route_get_pickup_times( | pub fn route_get_pickup_times( | ||||||
|     street: controller::Street, |     street: controller::Street, | ||||||
|     number: u32, |     number: u32, | ||||||
|     start_date: NaiveDate, |     start_date: controller::BasicDate, | ||||||
|     end_date: NaiveDate, |     end_date: controller::BasicDate, | ||||||
| ) -> Result<Json<Vec<controller::PickupTime>>, Status> { | ) -> Result<Json<Vec<controller::PickupTime>>, Status> { | ||||||
|     Err(Status::InternalServerError) |     match controller::get_pickup_times(street, number, start_date, end_date) { | ||||||
|  |         // TODO provide more meaningful status codes here
 | ||||||
|  |         Err(_) => Err(Status::InternalServerError), | ||||||
|  |         Ok(times) => Ok(Json(times)), | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue