parent
							
								
									27a61f8a9a
								
							
						
					
					
						commit
						c89841ad38
					
				|  | @ -56,6 +56,15 @@ dependencies = [ | |||
|  "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]] | ||||
| name = "atty" | ||||
| version = "0.2.14" | ||||
|  | @ -331,6 +340,7 @@ name = "fej" | |||
| version = "0.0.1" | ||||
| dependencies = [ | ||||
|  "chrono", | ||||
|  "regex", | ||||
|  "reqwest", | ||||
|  "rocket", | ||||
|  "rocket_contrib", | ||||
|  | @ -1190,6 +1200,23 @@ dependencies = [ | |||
|  "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]] | ||||
| name = "remove_dir_all" | ||||
| version = "0.5.3" | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ edition = "2018" | |||
| rocket = "0.4.7" | ||||
| serde = "1.0.124" | ||||
| chrono = "0.4.19" | ||||
| regex = "1.4.5" | ||||
| 
 | ||||
| [dependencies.reqwest] | ||||
| version = "0.11.2" | ||||
|  |  | |||
							
								
								
									
										6
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										6
									
								
								Makefile
								
								
								
								
							|  | @ -23,7 +23,7 @@ push: | |||
| 
 | ||||
| # Run
 | ||||
| run: | ||||
| 	@ cargo run | ||||
| 	@ RUST_BACKTRACE=1 cargo run | ||||
| .PHONY: run | ||||
| 
 | ||||
| 
 | ||||
|  | @ -36,6 +36,10 @@ format: | |||
| 	@ cargo fmt | ||||
| .PHONY: format | ||||
| 
 | ||||
| lint: | ||||
| 	@ cargo fmt -- --check | ||||
| .PHONY: lint | ||||
| 
 | ||||
| 
 | ||||
| # Documentation
 | ||||
| docs: | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| mod pickup_times; | ||||
| 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}; | ||||
| 
 | ||||
| ///// Return the known pickup times for the given street and/or city
 | ||||
|  |  | |||
|  | @ -1,36 +1,83 @@ | |||
| use super::search::Street; | ||||
| use chrono::NaiveDate; | ||||
| use regex::Regex; | ||||
| use rocket::http::RawStr; | ||||
| use rocket::request::FromFormValue; | ||||
| use serde::ser::{Serialize, SerializeStruct, Serializer}; | ||||
| use std::error::Error; | ||||
| 
 | ||||
| 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
 | ||||
| /// original API
 | ||||
| pub struct PickupTime { | ||||
|     date: NaiveDate, | ||||
|     date: BasicDate, | ||||
|     label: String, | ||||
|     classes: Vec<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( | ||||
|     street: Street, | ||||
|     number: u64, | ||||
|     start_date: NaiveDate, | ||||
|     end_date: NaiveDate, | ||||
|     number: u32, | ||||
|     start_date: BasicDate, | ||||
|     end_date: BasicDate, | ||||
| ) -> Result<Vec<PickupTime>, Box<dyn Error>> { | ||||
|     Ok(Vec::new()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,7 @@ | |||
| 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; | ||||
|  | @ -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
 | ||||
| pub struct Street { | ||||
|     pub name: String, | ||||
|  |  | |||
|  | @ -2,12 +2,11 @@ mod controller; | |||
| #[cfg(test)] | ||||
| mod tests; | ||||
| 
 | ||||
| use chrono::NaiveDate; | ||||
| use rocket::http::Status; | ||||
| use rocket_contrib::json::Json; | ||||
| 
 | ||||
| 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
 | ||||
|  | @ -15,7 +14,7 @@ pub fn routes() -> Vec<rocket::Route> { | |||
| pub fn route_search_streets(street: String) -> Result<Json<Vec<controller::Street>>, Status> { | ||||
|     match controller::search_streets(&street) { | ||||
|         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( | ||||
|     street: controller::Street, | ||||
|     number: u32, | ||||
|     start_date: NaiveDate, | ||||
|     end_date: NaiveDate, | ||||
|     start_date: controller::BasicDate, | ||||
|     end_date: controller::BasicDate, | ||||
| ) -> 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