Compare commits
No commits in common. "2e73d88ae9bdb207b3340ab6bf1f5a4e680c15d0" and "5dd2b3a8786aa4d61a012345e506fa7a2abfaa8b" have entirely different histories.
2e73d88ae9
...
5dd2b3a878
14 changed files with 88 additions and 130 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -174,16 +174,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
|
|
@ -350,7 +340,6 @@ name = "fej"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rocket",
|
||||
|
|
@ -1021,15 +1010,6 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.1.4"
|
||||
|
|
|
|||
16
Cargo.toml
16
Cargo.toml
|
|
@ -4,26 +4,12 @@ version = "0.0.1"
|
|||
authors = ["Jef Roosens <roosensjef@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "fej_lib"
|
||||
src = "src/lib.rs"
|
||||
test = true
|
||||
bench = true
|
||||
doc = true
|
||||
doctest = true
|
||||
|
||||
[[bin]]
|
||||
name = "fej"
|
||||
src = "src/main.rs"
|
||||
test = false
|
||||
bench = false
|
||||
doc = false
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.4.7"
|
||||
serde = "1.0.124"
|
||||
chrono = "0.4.19"
|
||||
chrono-tz = "0.5.3"
|
||||
regex = "1.4.5"
|
||||
|
||||
[dependencies.reqwest]
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ RUN apk update && apk add --no-cache openssl-dev build-base curl && \
|
|||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src/ ./src/
|
||||
|
||||
# Run the tests, don't want no broken docker image
|
||||
# And then finally, build the project
|
||||
# Finally, build the project
|
||||
# Thank the lords that this article exists
|
||||
# https://users.rust-lang.org/t/sigsegv-with-program-linked-against-openssl-in-an-alpine-container/52172
|
||||
# TODO add what these flags do & why they work
|
||||
RUN RUSTFLAGS="-C target-feature=-crt-static" cargo test && \
|
||||
RUSTFLAGS="-C target-feature=-crt-static" cargo build --release --bin fej
|
||||
RUN RUSTFLAGS="-C target-feature=-crt-static" cargo build --release
|
||||
|
||||
|
||||
# Now, we create the actual image
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -23,13 +23,13 @@ push:
|
|||
|
||||
# Run
|
||||
run:
|
||||
@ RUST_BACKTRACE=1 cargo run --bin fej
|
||||
@ RUST_BACKTRACE=1 cargo run
|
||||
.PHONY: run
|
||||
|
||||
|
||||
# Testing
|
||||
test:
|
||||
@ cargo test --no-fail-fast
|
||||
@ cargo test
|
||||
.PHONY: test
|
||||
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
// I can probably do this way easier using an external crate, I should do that
|
||||
use rocket::http::Status;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FejError {
|
||||
InvalidArgument,
|
||||
FailedRequest,
|
||||
|
|
@ -22,9 +20,3 @@ impl From<reqwest::Error> for FejError {
|
|||
FejError::FailedRequest
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::ParseError> for FejError {
|
||||
fn from(_: chrono::ParseError) -> FejError {
|
||||
FejError::InvalidArgument
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
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};
|
||||
|
|
@ -10,10 +8,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: &DateTime<Tz>,
|
||||
end_date: &DateTime<Tz>,
|
||||
street: Street,
|
||||
number: u32,
|
||||
start_date: BasicDate,
|
||||
end_date: BasicDate,
|
||||
) -> Result<Vec<PickupTime>, FejError> {
|
||||
let client = reqwest::Client::builder().cookie_store(true).build()?;
|
||||
|
||||
|
|
@ -33,22 +31,20 @@ pub fn get_pickup_times(
|
|||
.query(&[
|
||||
("_format", "json"),
|
||||
("type", ""),
|
||||
("start", &start_date.timestamp().to_string()),
|
||||
("end", &end_date.timestamp().to_string()),
|
||||
("start", &start_date.epoch().to_string()),
|
||||
("end", &end_date.epoch().to_string()),
|
||||
])
|
||||
.send()?;
|
||||
let data: Vec<HashMap<String, String>> = response.json()?;
|
||||
|
||||
let mut output: Vec<PickupTime> = Vec::new();
|
||||
|
||||
for map in data
|
||||
.iter()
|
||||
.filter(|m| m.contains_key("date") && m.contains_key("label"))
|
||||
{
|
||||
// Because we filtered the maps in the loop, we can safely us unwrap here
|
||||
if let Ok(date) = BasicDate::try_from(map.get("date").unwrap().as_str()) {
|
||||
output.push(PickupTime::new(date, map.get("label").unwrap().to_string()))
|
||||
}
|
||||
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(),
|
||||
map.get("label").unwrap().to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
|
|
|||
|
|
@ -13,17 +13,23 @@ const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garba
|
|||
///
|
||||
/// * `street` - name of the street
|
||||
/// * `city` - city the street is in
|
||||
// TODO find out how to do this async
|
||||
pub fn search_streets(street_name: &str) -> Result<Vec<Street>, FejError> {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?;
|
||||
let data: Vec<HashMap<String, String>> = response.json()?;
|
||||
|
||||
// This is pretty cool, filter_map first does get() on all the maps, and
|
||||
// then filters out any None values
|
||||
// Then, we do the same thing for streets
|
||||
Ok(data
|
||||
.iter()
|
||||
.filter_map(|m| m.get("value"))
|
||||
.filter_map(|v| Street::try_from(v.as_str()).ok())
|
||||
.collect())
|
||||
let mut output: Vec<Street> = Vec::new();
|
||||
|
||||
// We iterate over every item and extract the needed data
|
||||
for map in data.iter() {
|
||||
if let Some(value) = map.get("value") {
|
||||
match Street::try_from(value.as_str()) {
|
||||
Ok(street) => output.push(street),
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
use crate::errors::FejError;
|
||||
use chrono::{DateTime, NaiveDate, TimeZone};
|
||||
use chrono_tz::Europe::Brussels;
|
||||
use chrono_tz::Tz;
|
||||
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;
|
||||
|
||||
/// This class is a simple wrapper around chrono's DateTime. Its sole purpose
|
||||
/// is to avoid error E0117.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BasicDate(pub DateTime<Tz>);
|
||||
/// 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 allows us to use BasicDate as a query parameter in our routes.
|
||||
impl<'v> FromFormValue<'v> for BasicDate {
|
||||
|
|
@ -25,25 +41,29 @@ impl<'v> FromFormValue<'v> for BasicDate {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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)?,
|
||||
))
|
||||
/// We need this when deserializing BasicDate.
|
||||
impl ToString for BasicDate {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{}-{}-{}", self.year, self.month, self.day)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BasicDate> for String {
|
||||
fn from(date: &BasicDate) -> String {
|
||||
format!("{}", date.0.format("%Y-%m-%d"))
|
||||
/// 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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,18 +72,6 @@ impl Serialize for BasicDate {
|
|||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&String::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_invalid_date() {
|
||||
let val = "2012-13-12";
|
||||
let date = BasicDate::try_from(val);
|
||||
assert_eq!(date, Err(FejError::InvalidArgument));
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
mod controller;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use controller::structs::{BasicDate, PickupTime, Street};
|
||||
use controller::{get_pickup_times, search_streets};
|
||||
|
|
@ -22,9 +24,6 @@ pub fn route_get_pickup_times(
|
|||
end_date: BasicDate,
|
||||
) -> Result<Json<Vec<PickupTime>>, Status> {
|
||||
Ok(Json(get_pickup_times(
|
||||
&street,
|
||||
&number,
|
||||
&start_date.0,
|
||||
&end_date.0,
|
||||
street, number, start_date, end_date,
|
||||
)?))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use rocket::http::Status;
|
|||
use rocket::local::Client;
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite().mount("/", fej_lib::ivago::routes())
|
||||
rocket::ignite().mount("/", super::routes())
|
||||
}
|
||||
|
||||
/// Test 404 response
|
||||
11
src/lib.rs
11
src/lib.rs
|
|
@ -1,11 +0,0 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
// Route modules
|
||||
pub mod ivago;
|
||||
|
||||
// Helper modules
|
||||
pub mod catchers;
|
||||
pub mod errors;
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use fej_lib::{catchers, ivago};
|
||||
// Route modules
|
||||
mod catchers;
|
||||
mod errors;
|
||||
mod ivago;
|
||||
|
||||
fn rocket() -> rocket::Rocket {
|
||||
rocket::ignite()
|
||||
|
|
|
|||
Reference in a new issue