From 825fec349b859a300cf2561eb70bf65c3f3944e4 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 11:53:33 +0100 Subject: [PATCH 01/42] Switched to alpine-based Docker build --- Dockerfile | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 28d6728..f594de8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,33 @@ -# syntax=docker/dockerfile:1.2 -FROM rustlang/rust:nightly AS builder +FROM rust:alpine AS builder + +# Switch to the nightly build +RUN rustup default nightly WORKDIR /usr/src/app -# Build the app -COPY . . -RUN --mount=type=cache,target=/usr/src/app/target cargo install --path . +# Install build dependencies & create a new dummy project +# that we use as a build cache +RUN apk update && \ + apk add --no-cache openssl openssl-dev musl-dev && \ + cargo init --bin + +# Build the dependencies +# This is done separately to reduce average compile time +# Afterwards, we remove the dummy src directory +COPY Cargo.toml Cargo.lock . +RUN cargo build --release && \ + rm src/*.rs + +# Now, we build our own source code +COPY src/ ./src +RUN cargo install --path . -FROM debian:buster-slim +# Now, we create the actual image +FROM alpine:latest # Install dependencies -RUN apt update && apt install -y openssl +RUN apk update && apk add --no-cache openssl COPY --from=builder /usr/local/cargo/bin/rust-api /usr/local/bin/rust-api From 13b1bba127219ffd82057b977038930233d15f4f Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 11:57:10 +0100 Subject: [PATCH 02/42] Switched to regular docker instead of buildkit --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ce930eb..6a03816 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ release: .PHONY: release image: Dockerfile - @ DOCKER_BUILDKIT=1 docker build -t '$(IMAGE)' . + @ docker build -t '$(IMAGE)' . .PHONY: image From ad5bbc81be4c54a862b7a760b77346acc84ac9ad Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 21:54:38 +0100 Subject: [PATCH 03/42] Fixed tiny docker issue --- Dockerfile | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f594de8..a5148fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apk update && \ # Build the dependencies # This is done separately to reduce average compile time # Afterwards, we remove the dummy src directory -COPY Cargo.toml Cargo.lock . +COPY Cargo.toml Cargo.lock ./ RUN cargo build --release && \ rm src/*.rs diff --git a/Makefile b/Makefile index 6a03816..69f4a4a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -IMAGE := rust_api:latest +IMAGE := rust-api:latest shell := /bin/bash From 1641277bce1b8463076070bc61a881c1b861f6ae Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 21:58:58 +0100 Subject: [PATCH 04/42] Beginning ivago json calendar --- Cargo.lock | 214 +++++++++++++++++++++++++++++++- Cargo.toml | 2 +- src/ivago/controller/mod.rs | 43 ++++++- src/ivago/controller/structs.rs | 8 +- 4 files changed, 253 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4c597b..85dbcb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base64" version = "0.9.3" @@ -155,6 +161,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "const_fn" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" + [[package]] name = "cookie" version = "0.11.4" @@ -168,7 +180,34 @@ dependencies = [ "percent-encoding 2.1.0", "rand", "sha2", - "time", + "time 0.1.43", +] + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding 2.1.0", + "time 0.2.25", + "version_check 0.9.2", +] + +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie 0.14.4", + "idna 0.2.2", + "log 0.4.14", + "publicsuffix", + "serde", + "serde_json", + "time 0.2.25", + "url 2.2.1", ] [[package]] @@ -259,6 +298,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "encoding_rs" version = "0.8.28" @@ -530,7 +575,7 @@ dependencies = [ "log 0.3.9", "mime 0.2.6", "num_cpus", - "time", + "time 0.1.43", "traitobject", "typeable", "unicase", @@ -995,6 +1040,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -1013,6 +1064,16 @@ dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna 0.2.2", + "url 2.2.1", +] + [[package]] name = "quote" version = "0.6.13" @@ -1097,6 +1158,8 @@ checksum = "bf12057f289428dbf5c591c74bf10392e4a8003f993405a902f20117019022d4" dependencies = [ "base64 0.13.0", "bytes", + "cookie 0.14.4", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -1115,6 +1178,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "time 0.2.25", "tokio", "tokio-native-tls", "url 2.2.1", @@ -1139,7 +1203,7 @@ dependencies = [ "rocket_codegen", "rocket_http", "state", - "time", + "time 0.1.43", "toml", "version_check 0.9.2", "yansi", @@ -1179,14 +1243,14 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce364100ed7a1bf39257b69ebd014c1d5b4979b0d365d8c9ab0aa9c79645493d" dependencies = [ - "cookie", + "cookie 0.11.4", "hyper 0.10.16", "indexmap", "pear", "percent-encoding 1.0.1", "smallvec", "state", - "time", + "time 0.1.43", "unicode-xid 0.1.0", ] @@ -1200,6 +1264,15 @@ dependencies = [ "serde", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1254,11 +1327,40 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.9", + "syn 1.0.63", +] [[package]] name = "serde_json" @@ -1283,6 +1385,12 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "sha2" version = "0.9.3" @@ -1319,12 +1427,70 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "standback" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" +dependencies = [ + "version_check 0.9.2", +] + [[package]] name = "state" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.9", + "serde", + "serde_derive", + "syn 1.0.63", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.24", + "quote 1.0.9", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.63", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "subtle" version = "2.4.0" @@ -1377,6 +1543,44 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "time" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.2", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.24", + "quote 1.0.9", + "standback", + "syn 1.0.63", +] + [[package]] name = "tinyvec" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index a9c8c70..c994ff9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ serde = "1.0.124" [dependencies.reqwest] version = "0.11.2" default-features = true -features = ["blocking", "json"] +features = ["blocking", "json", "cookies"] [dependencies.rocket_contrib] version = "0.4.7" diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index b7242ca..382d3b3 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,6 +1,6 @@ use reqwest::blocking as reqwest; use std::collections::HashMap; -use std::convert::TryFrom; +use std::convert::{TryFrom, From}; use rocket::http::Status; use std::error::Error; @@ -13,6 +13,13 @@ const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; 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) + } +} + + /// Searches the Ivago API for streets in the given city /// /// # Arguments @@ -32,7 +39,7 @@ pub fn search_streets(street_name: &String) -> Result, Box output.push(street), Err(_) => continue, } @@ -49,6 +56,34 @@ pub fn search_streets(street_name: &String) -> Result, Box Vec { - Vec::new() +pub fn get_pickup_times(street: Street, number: u32) -> Result, Box> { + // The client needs to store cookies for the requests to work + let client = reqwest::Client::builder().cookie_store(true).build()?; + + // Create post data + let form = [ + ("garbage_type", ""), + ("ivago_street", String::from(street).as_str()), + ("number", format!("{}", number).as_str()), + ("form_id", "garbage_address_form"), + ]; + + // This request just serves to populate the cookies + client.post(BASE_URL) + .form(&form) + .send()?; + + let params = [ + ("_format", "json"), + ("type", ""), + + ] + +r2 = s.get("https://www.ivago.be/nl/particulier/garbage/pick-up/pickups?", + params={ + "_format": "json", + "type": "", + "start": "1622332800", + "end": "163861328100" + } } diff --git a/src/ivago/controller/structs.rs b/src/ivago/controller/structs.rs index 19a18f3..a5931d2 100644 --- a/src/ivago/controller/structs.rs +++ b/src/ivago/controller/structs.rs @@ -4,8 +4,8 @@ use serde::ser::{Serialize, Serializer, SerializeStruct}; /// Represents a street pub struct Street { - name: String, - city: String, + pub name: String, + pub city: String, } impl Serialize for Street { @@ -20,10 +20,10 @@ impl Serialize for Street { } } -impl TryFrom<&String> for Street { +impl TryFrom for Street { type Error = (); - fn try_from(value: &String) -> Result { + fn try_from(value: String) -> Result { if let Some(index) = value.find('(') { Ok(Street { name: (value[0 .. index - 1].trim()).to_string(), From a12ce07136899ea0b832c7b71ce19167155f8ef3 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 22:11:26 +0100 Subject: [PATCH 05/42] Small update to readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7dc9e6..6f4d607 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# rust-api +# Fej -An API written in Rust, which I used to learn the language. +Fej is an API written in Rust. I started this project to learn the language, +and really just have some fun. ## Project Structure From 207e735556d18c30ba5a6ef3f29f07d1edbae60e Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 13 Mar 2021 22:14:24 +0100 Subject: [PATCH 06/42] Added Ivago README --- src/ivago/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/ivago/README.md diff --git a/src/ivago/README.md b/src/ivago/README.md new file mode 100644 index 0000000..c69c1b4 --- /dev/null +++ b/src/ivago/README.md @@ -0,0 +1,5 @@ +# Ivago + +This part of the API is a wrapper around the Ivago website (Ivago being the +company that collects the trash in my city). Their city isn't exactly RESTful, +so this endpoint simply wraps it in a RESTful wrapper. From b42ea850cde431c6e09f26d11ab1b483ac2a59dd Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 15 Mar 2021 14:48:20 +0100 Subject: [PATCH 07/42] Switched to logic-based file structure --- src/ivago/controller/mod.rs | 116 ++++++++------------------- src/ivago/controller/pickup_times.rs | 19 +++++ src/ivago/controller/search.rs | 83 +++++++++++++++++++ src/ivago/controller/structs.rs | 55 ------------- src/ivago/mod.rs | 11 +-- 5 files changed, 140 insertions(+), 144 deletions(-) create mode 100644 src/ivago/controller/pickup_times.rs create mode 100644 src/ivago/controller/search.rs delete mode 100644 src/ivago/controller/structs.rs diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index 382d3b3..01f00a4 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,89 +1,41 @@ -use reqwest::blocking as reqwest; -use std::collections::HashMap; -use std::convert::{TryFrom, From}; -use rocket::http::Status; -use std::error::Error; - -pub mod structs; -use structs::{Street, Date, PickupTime}; +mod search; +pub use search::{Street, search_streets}; -/// The base URL where their API starts -const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; -const SEARCH_URL: &str ="https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; +///// Return the known pickup times for the given street and/or city +///// +///// # Arguments +///// +///// * `street` - name of the street +///// * `city` - city the street is in +//pub fn get_pickup_times(street: Street, number: u32) -> Result, Box> { +// // The client needs to store cookies for the requests to work +// let client = reqwest::Client::builder().cookie_store(true).build()?; +// // Create post data +// let form = [ +// ("garbage_type", ""), +// ("ivago_street", String::from(street).as_str()), +// ("number", format!("{}", number).as_str()), +// ("form_id", "garbage_address_form"), +// ]; -impl From for String { - fn from(street: Street) -> String { - format!("{} ({})", street.name, street.city) - } -} +// // This request just serves to populate the cookies +// client.post(BASE_URL) +// .form(&form) +// .send()?; - -/// Searches the Ivago API for streets in the given city -/// -/// # Arguments -/// -/// * `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: &String) -> Result, Box> { - let client = reqwest::Client::new(); - let response = client.get(SEARCH_URL) - .query(&[("q", street_name)]) - .send()?; - let data: Vec> = response.json()?; - - let mut output: Vec = 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) { - Ok(street) => output.push(street), - Err(_) => continue, - } - } - } - - Ok(output) -} - - -/// Return the known pickup times for the given street and/or city -/// -/// # Arguments -/// -/// * `street` - name of the street -/// * `city` - city the street is in -pub fn get_pickup_times(street: Street, number: u32) -> Result, Box> { - // The client needs to store cookies for the requests to work - let client = reqwest::Client::builder().cookie_store(true).build()?; - - // Create post data - let form = [ - ("garbage_type", ""), - ("ivago_street", String::from(street).as_str()), - ("number", format!("{}", number).as_str()), - ("form_id", "garbage_address_form"), - ]; - - // This request just serves to populate the cookies - client.post(BASE_URL) - .form(&form) - .send()?; - - let params = [ - ("_format", "json"), - ("type", ""), +// let params = [ +// ("_format", "json"), +// ("type", ""), - ] +// ] -r2 = s.get("https://www.ivago.be/nl/particulier/garbage/pick-up/pickups?", - params={ - "_format": "json", - "type": "", - "start": "1622332800", - "end": "163861328100" - } -} +//r2 = s.get("https://www.ivago.be/nl/particulier/garbage/pick-up/pickups?", +// params={ +// "_format": "json", +// "type": "", +// "start": "1622332800", +// "end": "163861328100" +// } +//} diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs new file mode 100644 index 0000000..98b8e64 --- /dev/null +++ b/src/ivago/controller/pickup_times.rs @@ -0,0 +1,19 @@ +const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; + + +/// Represents a timezoneless date +pub struct Date { + day: u8, + month: u8, + year: u32, +} + + +/// Represents a pickup time instance. All fields are a direct map of the +/// original API +pub struct PickupTime { + date: Date, + label: String, + classes: Vec, + url: String +} diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs new file mode 100644 index 0000000..d0282de --- /dev/null +++ b/src/ivago/controller/search.rs @@ -0,0 +1,83 @@ +use reqwest::blocking as reqwest; +use std::collections::HashMap; +use std::convert::TryFrom; +use serde::ser::{Serialize, Serializer, SerializeStruct}; +use std::error::Error; + + +/// 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(()) + } + } +} + + +/// Represents a street +pub struct Street { + pub name: String, + pub city: String, +} + + +/// Searches the Ivago API for streets in the given city +/// +/// # Arguments +/// +/// * `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: &String) -> Result, Box> { + let client = reqwest::Client::new(); + let response = client.get(SEARCH_URL) + .query(&[("q", street_name)]) + .send()?; + let data: Vec> = response.json()?; + + let mut output: Vec = 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.clone()) { + Ok(street) => output.push(street), + Err(_) => continue, + } + } + } + + Ok(output) +} diff --git a/src/ivago/controller/structs.rs b/src/ivago/controller/structs.rs deleted file mode 100644 index a5931d2..0000000 --- a/src/ivago/controller/structs.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::convert::TryFrom; -use serde::ser::{Serialize, Serializer, SerializeStruct}; - - -/// Represents a street -pub struct Street { - pub name: String, - pub city: String, -} - -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(()) - } - } -} - - -/// Represents a timezoneless date -pub struct Date { - day: u8, - month: u8, - year: u32, -} - - -/// Represents a pickup time instance. All fields are a direct map of the -/// original API -pub struct PickupTime { - date: Date, - label: String, - classes: Vec, - url: String -} diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index f597494..7ba799e 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -1,14 +1,11 @@ #[cfg(test)] mod tests; +mod controller; use rocket_contrib::json::Json; -mod controller; -use controller as ctrl; -use ctrl::structs::Street; - pub fn routes() -> Vec { routes![ - search_streets, + search_streets_json, ] } @@ -16,8 +13,8 @@ pub fn routes() -> Vec { // TODO make this async // TODO change this so it can return errors instead of empty json #[get("/search?", format="json")] -pub fn search_streets(street: String) -> Json> { - match ctrl::search_streets(&street) { +pub fn search_streets_json(street: String) -> Json> { + match controller::search_streets(&street) { Ok(streets) => Json(streets), Err(err) => { println!("{:?}", err); From c6d29f329c1f5518e51b8d694d74b413ffef0757 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 21 Mar 2021 16:18:53 +0100 Subject: [PATCH 08/42] Start ivago calendar endpoint --- Cargo.lock | 33 ++++++++++++++++++++++++++++ Cargo.toml | 1 + src/ivago/controller/mod.rs | 2 ++ src/ivago/controller/pickup_times.rs | 24 +++++++++++++------- src/ivago/mod.rs | 20 ++++++++++++----- 5 files changed, 66 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85dbcb8..d528ead 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,6 +152,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.43", + "winapi 0.3.9", +] + [[package]] name = "cipher" version = "0.2.5" @@ -902,6 +915,25 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -1258,6 +1290,7 @@ dependencies = [ name = "rust-api" version = "0.1.0" dependencies = [ + "chrono", "reqwest", "rocket", "rocket_contrib", diff --git a/Cargo.toml b/Cargo.toml index c994ff9..0442ebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] rocket = "0.4.7" serde = "1.0.124" +chrono = "0.4.19" [dependencies.reqwest] version = "0.11.2" diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index 01f00a4..e2555c5 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,5 +1,7 @@ mod search; +mod pickup_times; pub use search::{Street, search_streets}; +pub use pickup_times::{get_pickup_times, PickupTime}; ///// Return the known pickup times for the given street and/or city diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 98b8e64..37d84c4 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,19 +1,27 @@ +use std::error::Error; +use chrono::NaiveDate; +use super::search::Street; + + const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; -/// Represents a timezoneless date -pub struct Date { - day: u8, - month: u8, - year: u32, -} - /// Represents a pickup time instance. All fields are a direct map of the /// original API pub struct PickupTime { - date: Date, + date: NaiveDate, label: String, classes: Vec, url: String } + + +pub fn get_pickup_times( + street: Street, + number: u64, + start_date: NaiveDate, + end_date: NaiveDate +) -> Result, Box> { + Ok(Vec::new()) +} diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 7ba799e..8547130 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -2,10 +2,11 @@ mod controller; use rocket_contrib::json::Json; +use chrono::NaiveDate; pub fn routes() -> Vec { routes![ - search_streets_json, + route_search_streets, ] } @@ -13,12 +14,19 @@ pub fn routes() -> Vec { // TODO make this async // TODO change this so it can return errors instead of empty json #[get("/search?", format="json")] -pub fn search_streets_json(street: String) -> Json> { +pub fn route_search_streets(street: String) -> Json> { match controller::search_streets(&street) { Ok(streets) => Json(streets), - Err(err) => { - println!("{:?}", err); - Json(Vec::new()) - }, + Err(err) => Json(Vec::new()), } } + +#[get("/?&&&")] +pub fn route_get_pickup_times( + street: controller::Street, + number: u64, + start_date: NaiveDate, + end_date: NaiveDate + ) -> Json> { + +} From 59dfc33e8f8994cf75b6069e5d4ed0e47dddcba5 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 21 Mar 2021 16:29:53 +0100 Subject: [PATCH 09/42] Updated Makefile to add pushing of image --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 69f4a4a..76b08f0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -IMAGE := rust-api:latest +IMAGE := chewingbever/fej +TAG := 0.1-dev shell := /bin/bash @@ -15,9 +16,12 @@ release: .PHONY: release image: Dockerfile - @ docker build -t '$(IMAGE)' . + @ docker build -t '$(IMAGE):$(TAG)' . .PHONY: image +push: image + @ docker push '$(IMAGE):$(TAG)' +.PHONY: push # Run run: From 35fc9be5dafa4cc8bf9ec9a988754bed3625283e Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 22 Mar 2021 16:36:01 +0100 Subject: [PATCH 10/42] Failed attempt at fixing docker build --- Dockerfile | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index a5148fe..9de43f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,28 @@ -FROM rust:alpine AS builder +FROM alpine:latest AS builder + +ENV PATH "$PATH:/root/.cargo/bin" # Switch to the nightly build -RUN rustup default nightly - WORKDIR /usr/src/app -# Install build dependencies & create a new dummy project -# that we use as a build cache -RUN apk update && \ - apk add --no-cache openssl openssl-dev musl-dev && \ - cargo init --bin +# Install build dependencies, install rustup & create dummy project +RUN apk update && apk add --no-cache openssl-dev build-base curl && \ + { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly; } && \ + rustup target add x86_64-unknown-linux-musl # Build the dependencies # This is done separately to reduce average compile time # Afterwards, we remove the dummy src directory -COPY Cargo.toml Cargo.lock ./ -RUN cargo build --release && \ - rm src/*.rs - # Now, we build our own source code -COPY src/ ./src -RUN cargo install --path . +COPY Cargo.toml Cargo.lock ./ +COPY src/ ./src/ +RUN cargo build --release --target x86_64-unknown-linux-musl # Now, we create the actual image -FROM alpine:latest +FROM scratch # Install dependencies -RUN apk update && apk add --no-cache openssl +COPY --from=builder /usr/src/app/target/x86_64-unknown-linux-musl/release/rust-api /rust-api -COPY --from=builder /usr/local/cargo/bin/rust-api /usr/local/bin/rust-api - -CMD ["rust-api"] +CMD ["/rust-api"] From 82b2927b3393db44d80bc396eb4a997a062fa956 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 23 Mar 2021 00:08:54 +0100 Subject: [PATCH 11/42] Fixed broken Docker image --- Cargo.toml | 4 ++-- Dockerfile | 30 +++++++++++++++++------------- Makefile | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c994ff9..a34a58e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "rust-api" -version = "0.1.0" +name = "fej" +version = "0.0.1" authors = ["Jef Roosens "] edition = "2018" diff --git a/Dockerfile b/Dockerfile index 9de43f3..4a3ed8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,32 @@ +# We use a multi-stage build to end up with a very small final image FROM alpine:latest AS builder ENV PATH "$PATH:/root/.cargo/bin" -# Switch to the nightly build WORKDIR /usr/src/app -# Install build dependencies, install rustup & create dummy project +# Install build dependencies, rustup & rust's nightly build & toolchain RUN apk update && apk add --no-cache openssl-dev build-base curl && \ - { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly; } && \ - rustup target add x86_64-unknown-linux-musl + { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly; } -# Build the dependencies -# This is done separately to reduce average compile time -# Afterwards, we remove the dummy src directory -# Now, we build our own source code +# Copy source code over to builder COPY Cargo.toml Cargo.lock ./ COPY src/ ./src/ -RUN cargo build --release --target x86_64-unknown-linux-musl + +# 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 build --release # Now, we create the actual image -FROM scratch +FROM alpine:latest -# Install dependencies -COPY --from=builder /usr/src/app/target/x86_64-unknown-linux-musl/release/rust-api /rust-api +# Install some dynamic libraries needed for everything to work +RUN apk update && apk add --no-cache openssl libgcc -CMD ["/rust-api"] +# Copy binary over to final image +COPY --from=builder /usr/src/app/target/release/fej /usr/local/bin/fej + +CMD ["/usr/local/bin/fej"] diff --git a/Makefile b/Makefile index 76b08f0..603ad5d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ IMAGE := chewingbever/fej -TAG := 0.1-dev +TAG := 0.0.1-dev shell := /bin/bash From cedcc3fc9f7ac0ec9ccd9f0fece1b5fbd9843e3e Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 23 Mar 2021 09:37:18 +0100 Subject: [PATCH 12/42] Started build script --- Makefile | 6 +++--- build | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100755 build diff --git a/Makefile b/Makefile index 603ad5d..e878d28 100644 --- a/Makefile +++ b/Makefile @@ -16,11 +16,11 @@ release: .PHONY: release image: Dockerfile - @ docker build -t '$(IMAGE):$(TAG)' . + @ bash ./build '$(IMAGE)' .PHONY: image -push: image - @ docker push '$(IMAGE):$(TAG)' +push: + @ bash ./build '$(IMAGE)' push .PHONY: push # Run diff --git a/build b/build new file mode 100755 index 0000000..480b6f1 --- /dev/null +++ b/build @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# Simple guard to check input args +[[ $# -eq 1 ]] || [[ $# -eq 2 ]] || { + >&2 echo "Usage: ./build IMAGE [ACTION]" + exit 1 +} + +# Extract current version from Cargo.toml & get current branch +patch_version="$(grep -Po '(?<=version = ").*(?=")' Cargo.toml | head -n1)" +major_version="$(echo "$patch_version" | + sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)/\1/')" +minor_version="$(echo "$patch_version" | + sed -E 's/([0-9]+).([0-9]+).([0-9]+)/\1.\2/')" +branch="$(git branch --show-current)" + +if [[ "$branch" = "master" ]]; then + tags=("$patch_version" "$minor_version" "$major_version" ) + +elif [[ "$branch" = "develop" ]]; then + tags=("$patch_version-dev" "$minor_version-dev" "$major_version-dev" ) + +else + tags=("$branch") + +fi + +tag_flags=() + +for tag in "${tags[@]}"; do + tag_flags+=("-t '$1:$tag'") + +done + +# Run the actual build command +docker build $tag_flags . + +if [[ "$2" = push ]]; then + for tag in "${tags[@]}"; do + docker push "$1:$tag" + done + + elif [[ "$2" = run ]]; then + docker run \ + --rm \ + --interactive \ + --tty \ + --publish 8000:8000 \ + "$1:$tags" +fi From ab94583e62fae1db9f300b702335057260b52525 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 23 Mar 2021 10:42:20 +0100 Subject: [PATCH 13/42] Fixed wrinkles in build script --- Cargo.lock | 20 ++++++++++---------- Makefile | 6 ++---- build | 9 +++++++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85dbcb8..d7b6fcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,6 +313,16 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "fej" +version = "0.0.1" +dependencies = [ + "reqwest", + "rocket", + "rocket_contrib", + "serde", +] + [[package]] name = "filetime" version = "0.2.14" @@ -1254,16 +1264,6 @@ dependencies = [ "unicode-xid 0.1.0", ] -[[package]] -name = "rust-api" -version = "0.1.0" -dependencies = [ - "reqwest", - "rocket", - "rocket_contrib", - "serde", -] - [[package]] name = "rustc_version" version = "0.2.3" diff --git a/Makefile b/Makefile index e878d28..4017a77 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ IMAGE := chewingbever/fej -TAG := 0.0.1-dev -shell := /bin/bash all: debug @@ -16,11 +14,11 @@ release: .PHONY: release image: Dockerfile - @ bash ./build '$(IMAGE)' + @ ./build '$(IMAGE)' .PHONY: image push: - @ bash ./build '$(IMAGE)' push + @ ./build '$(IMAGE)' push .PHONY: push # Run diff --git a/build b/build index 480b6f1..85adacf 100755 --- a/build +++ b/build @@ -15,7 +15,7 @@ minor_version="$(echo "$patch_version" | branch="$(git branch --show-current)" if [[ "$branch" = "master" ]]; then - tags=("$patch_version" "$minor_version" "$major_version" ) + tags=("$patch_version" "$minor_version" "$major_version" "latest" ) elif [[ "$branch" = "develop" ]]; then tags=("$patch_version-dev" "$minor_version-dev" "$major_version-dev" ) @@ -28,7 +28,7 @@ fi tag_flags=() for tag in "${tags[@]}"; do - tag_flags+=("-t '$1:$tag'") + tag_flags+=("-t $1:$tag") done @@ -36,6 +36,11 @@ done docker build $tag_flags . if [[ "$2" = push ]]; then + [[ "$branch" =~ ^develop|master$ ]] || { + >&2 echo "You can only push from develop or master." + exit 2 + } + for tag in "${tags[@]}"; do docker push "$1:$tag" done From e81205bef73cfbb88e4b98e34cde51e4004f5695 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 23 Mar 2021 10:55:00 +0100 Subject: [PATCH 14/42] Added working auto-tagging --- build | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build b/build index 85adacf..0117f6a 100755 --- a/build +++ b/build @@ -15,25 +15,18 @@ minor_version="$(echo "$patch_version" | branch="$(git branch --show-current)" if [[ "$branch" = "master" ]]; then - tags=("$patch_version" "$minor_version" "$major_version" "latest" ) + tags=("$patch_version" "$minor_version" "$major_version" "latest") elif [[ "$branch" = "develop" ]]; then - tags=("$patch_version-dev" "$minor_version-dev" "$major_version-dev" ) + tags=("$patch_version-dev" "$minor_version-dev" "$major_version-dev" "dev") else tags=("$branch") fi -tag_flags=() - -for tag in "${tags[@]}"; do - tag_flags+=("-t $1:$tag") - -done - # Run the actual build command -docker build $tag_flags . +docker build -t "$1:$tags" . if [[ "$2" = push ]]; then [[ "$branch" =~ ^develop|master$ ]] || { @@ -42,7 +35,14 @@ if [[ "$2" = push ]]; then } for tag in "${tags[@]}"; do + # Create the tag + docker tag "$1:$tags" "$1:$tag" + + # Push the tag docker push "$1:$tag" + + # Remove the tag again, if it's not the main tag + [[ "$tag" != "$tags" ]] && docker rmi "$1:$tag" done elif [[ "$2" = run ]]; then From 7f8da3e58413df1030b445ec3c13c49a68fea920 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 23 Mar 2021 12:16:01 +0100 Subject: [PATCH 15/42] Removed unnecessary content type --- src/ivago/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 7ba799e..ad71ca9 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -12,7 +12,7 @@ pub fn routes() -> Vec { // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange // TODO make this async // TODO change this so it can return errors instead of empty json -#[get("/search?", format="json")] +#[get("/search?")] pub fn search_streets_json(street: String) -> Json> { match controller::search_streets(&street) { Ok(streets) => Json(streets), From f804c01849c015434c31c44fdd5c7f72ea671dc5 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Fri, 2 Apr 2021 21:08:50 +0200 Subject: [PATCH 16/42] Added git hooks --- .hooks/commit-msg | 23 +++++++++++++++++++++++ .hooks/pre-commit | 13 +++++++++++++ 2 files changed, 36 insertions(+) create mode 100755 .hooks/commit-msg create mode 100755 .hooks/pre-commit diff --git a/.hooks/commit-msg b/.hooks/commit-msg new file mode 100755 index 0000000..284ee8d --- /dev/null +++ b/.hooks/commit-msg @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# This hook checks if the commit message ends with an issue number, and if not, +# tries to derive that number from the branch name + +branch=`git rev-parse --abbrev-ref HEAD` + +# This check doesn't need to run when commiting to develop/master +[[ "$branch" =~ ^master|develop$ ]] && exit 0 + +issue_num=`echo "$branch" | grep -Po '^[0-9]+(?=-)'` + +# Check if issue number is already present +if ! grep -q '([0-9]\+)$' "$1"; then + # Error out if we can't derive issue number + [[ -z "$issue_num" ]] && { + >&2 echo "Couldn't derive issue number from branch. Please add one manually."; + exit 1; + } + + # Append issue number, and remove all comments + echo "[#$issue_num]" "$(cat "$1")" > "$1" +fi diff --git a/.hooks/pre-commit b/.hooks/pre-commit new file mode 100755 index 0000000..d9dd92d --- /dev/null +++ b/.hooks/pre-commit @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# This hook lints the code, and if we're on develop or master, also forces the tests to pass. + +branch=`git rev-parse --abbrev-ref HEAD` + +# TODO should we add release branches here as well? +if [[ "$branch" =~ ^master|develop$ ]]; then + make test > /dev/null 2>&1 || { + >&2 echo "Tests failed. check 'make test' for more info."; + exit 1; + } +fi From e3f134a9bf770664078e1b2ad85d57bf48a6b710 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Fri, 2 Apr 2021 21:20:36 +0200 Subject: [PATCH 17/42] Added code formatting --- .hooks/pre-commit | 1 + Makefile | 4 +++ src/hello/mod.rs | 9 +++---- src/hello/tests.rs | 2 +- src/ivago/controller/mod.rs | 7 +++-- src/ivago/controller/pickup_times.rs | 12 +++------ src/ivago/controller/search.rs | 39 +++++++++++----------------- src/ivago/mod.rs | 14 +++++----- src/ivago/tests.rs | 1 + src/main.rs | 3 ++- 10 files changed, 40 insertions(+), 52 deletions(-) diff --git a/.hooks/pre-commit b/.hooks/pre-commit index d9dd92d..a2dd254 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -1,6 +1,7 @@ #!/usr/bin/env bash # This hook lints the code, and if we're on develop or master, also forces the tests to pass. +cargo fmt -- --check branch=`git rev-parse --abbrev-ref HEAD` diff --git a/Makefile b/Makefile index 4017a77..2f9e53d 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,10 @@ test: @ cargo test .PHONY: test +format: + @ cargo fmt +.PHONY: format + # Documentation docs: diff --git a/src/hello/mod.rs b/src/hello/mod.rs index c15060b..608d234 100644 --- a/src/hello/mod.rs +++ b/src/hello/mod.rs @@ -1,11 +1,8 @@ -#[cfg(test)] mod tests; +#[cfg(test)] +mod tests; pub fn routes() -> Vec { - routes![ - world, - hello, - name_age - ] + routes![world, hello, name_age] } #[get("/world")] diff --git a/src/hello/tests.rs b/src/hello/tests.rs index d4f7096..e19264d 100644 --- a/src/hello/tests.rs +++ b/src/hello/tests.rs @@ -1,5 +1,5 @@ -use rocket::local::Client; use rocket::http::Status; +use rocket::local::Client; fn rocket() -> rocket::Rocket { rocket::ignite().mount("/", super::routes()) diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index e2555c5..6ff9dff 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,8 +1,7 @@ -mod search; mod pickup_times; -pub use search::{Street, search_streets}; +mod search; pub use pickup_times::{get_pickup_times, PickupTime}; - +pub use search::{search_streets, Street}; ///// Return the known pickup times for the given street and/or city ///// @@ -30,7 +29,7 @@ pub use pickup_times::{get_pickup_times, PickupTime}; // let params = [ // ("_format", "json"), // ("type", ""), - + // ] //r2 = s.get("https://www.ivago.be/nl/particulier/garbage/pick-up/pickups?", diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 37d84c4..aa86a1d 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,27 +1,23 @@ -use std::error::Error; -use chrono::NaiveDate; use super::search::Street; - +use chrono::NaiveDate; +use std::error::Error; const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; - - /// Represents a pickup time instance. All fields are a direct map of the /// original API pub struct PickupTime { date: NaiveDate, label: String, classes: Vec, - url: String + url: String, } - pub fn get_pickup_times( street: Street, number: u64, start_date: NaiveDate, - end_date: NaiveDate + end_date: NaiveDate, ) -> Result, Box> { Ok(Vec::new()) } diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index d0282de..2df30b9 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -1,13 +1,11 @@ use reqwest::blocking as reqwest; +use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::collections::HashMap; use std::convert::TryFrom; -use serde::ser::{Serialize, Serializer, SerializeStruct}; use std::error::Error; - /// Endpoint for the search feature -const SEARCH_URL: &str ="https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; - +const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; impl From for String { fn from(street: Street) -> String { @@ -15,44 +13,39 @@ impl From for String { } } - 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() - } + 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(), + name: (value[0..index - 1].trim()).to_string(), + city: (value[index + 1..value.len() - 1].trim()).to_string(), }) - - }else { + } else { Err(()) } } } - /// Represents a street pub struct Street { pub name: String, pub city: String, } - /// Searches the Ivago API for streets in the given city /// /// # Arguments @@ -62,9 +55,7 @@ pub struct Street { // TODO find out how to do this async pub fn search_streets(street_name: &String) -> Result, Box> { let client = reqwest::Client::new(); - let response = client.get(SEARCH_URL) - .query(&[("q", street_name)]) - .send()?; + let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; let data: Vec> = response.json()?; let mut output: Vec = Vec::new(); diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index d24dc28..9cf77dc 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -1,13 +1,12 @@ -#[cfg(test)] mod tests; mod controller; +#[cfg(test)] +mod tests; -use rocket_contrib::json::Json; use chrono::NaiveDate; +use rocket_contrib::json::Json; pub fn routes() -> Vec { - routes![ - route_search_streets, - ] + routes![route_search_streets,] } // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange @@ -26,7 +25,6 @@ pub fn route_get_pickup_times( street: controller::Street, number: u64, start_date: NaiveDate, - end_date: NaiveDate - ) -> Json> { - + end_date: NaiveDate, +) -> Json> { } diff --git a/src/ivago/tests.rs b/src/ivago/tests.rs index e69de29..8b13789 100644 --- a/src/ivago/tests.rs +++ b/src/ivago/tests.rs @@ -0,0 +1 @@ + diff --git a/src/main.rs b/src/main.rs index 43348ab..8a5ea07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![feature(proc_macro_hygiene, decl_macro)] -#[macro_use] extern crate rocket; +#[macro_use] +extern crate rocket; // Route modules mod hello; From e3174f21afeaab92924c48fe4fcf870e86d14e83 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 3 Apr 2021 20:47:39 +0200 Subject: [PATCH 18/42] [#4] We now properly return error values --- src/ivago/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 9cf77dc..386bf36 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -3,6 +3,7 @@ mod controller; mod tests; use chrono::NaiveDate; +use rocket::http::Status; use rocket_contrib::json::Json; pub fn routes() -> Vec { @@ -10,21 +11,20 @@ pub fn routes() -> Vec { } // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange -// TODO make this async -// TODO change this so it can return errors instead of empty json #[get("/search?")] -pub fn search_streets_json(street: String) -> Json> { +pub fn route_search_streets(street: String) -> Result>, Status> { match controller::search_streets(&street) { - Ok(streets) => Json(streets), - Err(err) => Json(Vec::new()), + Ok(streets) => Ok(Json(streets)), + Err(err) => Err(Status::InternalServerError), } } #[get("/?&&&")] pub fn route_get_pickup_times( street: controller::Street, - number: u64, + number: u32, start_date: NaiveDate, end_date: NaiveDate, -) -> Json> { +) -> Result>, Status> { + Err(Status::InternalServerError) } From 27a61f8a9a1571793f50880ffbad01a4a9da1551 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sat, 3 Apr 2021 22:40:22 +0200 Subject: [PATCH 19/42] [#4] Started implementing needed traits (#4) --- src/ivago/controller/pickup_times.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index aa86a1d..1f037a2 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,5 +1,7 @@ use super::search::Street; use chrono::NaiveDate; +use rocket::http::RawStr; +use rocket::request::FromFormValue; use std::error::Error; const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; @@ -13,6 +15,17 @@ pub struct PickupTime { url: String, } +impl<'v> FromFormValue<'v> for NaiveDate { + type Error = &'v RawStr; + + fn from_form_value(form_value: &'v RawStr) -> Result { + 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, From c89841ad38cd28760323737c30fa6bc9f91b7e2e Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 09:57:19 +0200 Subject: [PATCH 20/42] [#4] Finally got proper form value handling (#1) --- Cargo.lock | 27 ++++++++++ Cargo.toml | 1 + Makefile | 6 ++- src/ivago/controller/mod.rs | 2 +- src/ivago/controller/pickup_times.rs | 79 ++++++++++++++++++++++------ src/ivago/controller/search.rs | 20 +++++++ src/ivago/mod.rs | 15 +++--- 7 files changed, 126 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e248c93..9c0d5ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 53b452c..6969693 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/Makefile b/Makefile index 2f9e53d..a1baf59 100644 --- a/Makefile +++ b/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: diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index 6ff9dff..8e3cb62 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -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 diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 1f037a2..e4ce656 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -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 { + // 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(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl Serialize for PickupTime { + fn serialize(&self, serializer: S) -> Result + 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, url: String, } -impl<'v> FromFormValue<'v> for NaiveDate { - type Error = &'v RawStr; - - fn from_form_value(form_value: &'v RawStr) -> Result { - 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, Box> { Ok(Vec::new()) } diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 2df30b9..be81254 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -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 for Street { } } +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, diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 386bf36..33ab64e 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -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 { - 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 { pub fn route_search_streets(street: String) -> Result>, 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 Result>, 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)), + } } From 01276004e1fb20a7b4108dc1a95b1ffafbaf1bc8 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 11:39:40 +0200 Subject: [PATCH 21/42] [#4] Added BasicDate type conversions --- src/ivago/controller/pickup_times.rs | 51 ++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index e4ce656..174e538 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,11 +1,17 @@ use super::search::Street; +use chrono::{FixedOffset, TimeZone}; use regex::Regex; +use reqwest::blocking as reqwest; use rocket::http::RawStr; use rocket::request::FromFormValue; -use serde::ser::{Serialize, SerializeStruct, Serializer}; +use serde::ser::{SerializeStruct, Serializer}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::convert::From; use std::error::Error; 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. @@ -49,6 +55,16 @@ impl Serialize for BasicDate { } } +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) + .and_hms(0, 0, 0) + .timestamp() + } +} + impl Serialize for PickupTime { fn serialize(&self, serializer: S) -> Result where @@ -66,6 +82,7 @@ impl Serialize for PickupTime { /// Represents a pickup time instance. All fields are a direct map of the /// original API +#[derive(Deserialize)] pub struct PickupTime { date: BasicDate, label: String, @@ -79,5 +96,35 @@ pub fn get_pickup_times( start_date: BasicDate, end_date: BasicDate, ) -> Result, Box> { - Ok(Vec::new()) + let client = reqwest::Client::builder().cookie_store(true).build()?; + + // This populates the cookies with the necessary values + client + .post(BASE_URL) + .form(&[ + ("garbage_type", ""), + ("ivago_street", &String::from(street)), + ("number", &number.to_string()), + ("form_id", "garbage_address_form"), + ]) + .send()?; + + let response = client + .get(CAL_URL) + .query(&[ + ("_format", "json"), + ("type", ""), + ("start", &i64::from(start_date).to_string()), + ("end", &i64::from(end_date).to_string()), + ]) + .send()?; + let data: Vec> = response.json()?; + + let mut output: Vec = Vec::new(); + + for map in data.iter() { + if let Some(value) = map.get("value") {} + } + + Ok() } From 0c7d55647e08cf8d66a498c73d5664e72bbcb03b Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 12:06:33 +0200 Subject: [PATCH 22/42] [#4] JSON requests now work! --- src/ivago/controller/pickup_times.rs | 48 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 174e538..58d3f29 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -7,7 +7,7 @@ use rocket::request::FromFormValue; use serde::ser::{SerializeStruct, Serializer}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::convert::From; +use std::convert::{From, TryFrom}; use std::error::Error; const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; @@ -26,9 +26,29 @@ impl<'v> FromFormValue<'v> for BasicDate { fn from_form_value(form_value: &'v RawStr) -> Result { // Beautiful how this exact example is in the docs + match BasicDate::try_from(form_value.as_str()) { + Err(_) => Err(form_value), + // Here, we can assume these parses will work, because the regex + // didn't fail + Ok(date) => Ok(date), + } + } +} + +impl ToString for BasicDate { + fn to_string(&self) -> String { + format!("{}-{}-{}", self.year, self.month, self.day) + } +} + +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(form_value) { - None => Err(form_value), + + match re.captures(s) { + None => Err(()), // Here, we can assume these parses will work, because the regex // didn't fail Some(caps) => Ok(BasicDate { @@ -40,12 +60,6 @@ impl<'v> FromFormValue<'v> for BasicDate { } } -impl ToString for BasicDate { - fn to_string(&self) -> String { - format!("{}-{}-{}", self.year, self.month, self.day) - } -} - impl Serialize for BasicDate { fn serialize(&self, serializer: S) -> Result where @@ -82,7 +96,6 @@ impl Serialize for PickupTime { /// Represents a pickup time instance. All fields are a direct map of the /// original API -#[derive(Deserialize)] pub struct PickupTime { date: BasicDate, label: String, @@ -123,8 +136,19 @@ pub fn get_pickup_times( let mut output: Vec = Vec::new(); for map in data.iter() { - if let Some(value) = map.get("value") {} + output.push(PickupTime { + // TODO should I check here if the parsing worked? + date: BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(), + label: map.get("label").unwrap().to_string(), + classes: map + .get("classes") + .unwrap() + .split_whitespace() + .map(|x| String::from(x)) + .collect(), + url: map.get("url").unwrap().to_string(), + }) } - Ok() + Ok(output) } From eab31e5e913f4a05c5590cbe8a28de168400740c Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 13:03:08 +0200 Subject: [PATCH 23/42] [#9] Added general error module --- src/errors.rs | 23 +++++++++++++++++++++++ src/ivago/controller/search.rs | 3 ++- src/ivago/mod.rs | 7 +++---- src/main.rs | 1 + 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/errors.rs diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..6403235 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,23 @@ +use reqwest::Error; +use rocket::http::Status; + +pub enum FejError { + InvalidArgument, + FailedRequest, +} + +impl From for Status { + fn from(err: FejError) -> Status { + match err { + FejError::InvalidArgument => Status::BadRequest, + FejError::FailedRequest => Status::InternalServerError, + } + } +} + +// TODO make this more advanced where possible +impl From for FejError { + fn from(_: Error) -> FejError { + FejError::FailedRequest + } +} diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index be81254..926d282 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -1,3 +1,4 @@ +use crate::errors::FejError; use regex::Regex; use reqwest::blocking as reqwest; use rocket::http::RawStr; @@ -73,7 +74,7 @@ pub struct Street { /// * `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: &String) -> Result, Box> { +pub fn search_streets(street_name: &String) -> Result, FejError> { let client = reqwest::Client::new(); let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; let data: Vec> = response.json()?; diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 33ab64e..c173aab 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -2,6 +2,7 @@ mod controller; #[cfg(test)] mod tests; +use crate::errors::FejError; use rocket::http::Status; use rocket_contrib::json::Json; @@ -12,10 +13,8 @@ pub fn routes() -> Vec { // URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange #[get("/search?")] pub fn route_search_streets(street: String) -> Result>, Status> { - match controller::search_streets(&street) { - Ok(streets) => Ok(Json(streets)), - Err(_) => Err(Status::InternalServerError), - } + let result = controller::search_streets(&street)?; + Ok(Json(result)) } #[get("/?&&&")] diff --git a/src/main.rs b/src/main.rs index 8a5ea07..882db7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate rocket; // Route modules +mod errors; mod hello; mod ivago; From df6e245030e62d1bcae1f6bdfac2d56d9a5520b8 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 14:14:14 +0200 Subject: [PATCH 24/42] [#9] Moved calendar function to new errors system --- src/ivago/controller/pickup_times.rs | 6 +++--- src/ivago/controller/search.rs | 1 - src/ivago/mod.rs | 13 ++++--------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 58d3f29..202f8fc 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,14 +1,14 @@ use super::search::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::{Deserialize, Serialize}; +use serde::Serialize; use std::collections::HashMap; use std::convert::{From, TryFrom}; -use std::error::Error; 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"; @@ -108,7 +108,7 @@ pub fn get_pickup_times( number: u32, start_date: BasicDate, end_date: BasicDate, -) -> Result, Box> { +) -> Result, FejError> { let client = reqwest::Client::builder().cookie_store(true).build()?; // This populates the cookies with the necessary values diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 926d282..5037bb4 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -6,7 +6,6 @@ use rocket::request::FromFormValue; use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::collections::HashMap; use std::convert::TryFrom; -use std::error::Error; /// Endpoint for the search feature const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index c173aab..541b27a 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -2,7 +2,6 @@ mod controller; #[cfg(test)] mod tests; -use crate::errors::FejError; use rocket::http::Status; use rocket_contrib::json::Json; @@ -10,11 +9,9 @@ pub fn routes() -> Vec { routes![route_search_streets, route_get_pickup_times] } -// URL: https://www.ivago.be/nl/particulier/autocomplete/garbage/streets?q=Lange #[get("/search?")] pub fn route_search_streets(street: String) -> Result>, Status> { - let result = controller::search_streets(&street)?; - Ok(Json(result)) + Ok(Json(controller::search_streets(&street)?)) } #[get("/?&&&")] @@ -24,9 +21,7 @@ pub fn route_get_pickup_times( start_date: controller::BasicDate, end_date: controller::BasicDate, ) -> Result>, Status> { - 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)), - } + Ok(Json(controller::get_pickup_times( + street, number, start_date, end_date, + )?)) } From 64161ddcda3056a388d8c769e59fc08de0796b3a Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 14:18:54 +0200 Subject: [PATCH 25/42] Removed unnecessary commented out code --- src/ivago/controller/mod.rs | 38 ------------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index 8e3cb62..ef745d0 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -2,41 +2,3 @@ mod pickup_times; mod search; 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 -///// -///// # Arguments -///// -///// * `street` - name of the street -///// * `city` - city the street is in -//pub fn get_pickup_times(street: Street, number: u32) -> Result, Box> { -// // The client needs to store cookies for the requests to work -// let client = reqwest::Client::builder().cookie_store(true).build()?; - -// // Create post data -// let form = [ -// ("garbage_type", ""), -// ("ivago_street", String::from(street).as_str()), -// ("number", format!("{}", number).as_str()), -// ("form_id", "garbage_address_form"), -// ]; - -// // This request just serves to populate the cookies -// client.post(BASE_URL) -// .form(&form) -// .send()?; - -// let params = [ -// ("_format", "json"), -// ("type", ""), - -// ] - -//r2 = s.get("https://www.ivago.be/nl/particulier/garbage/pick-up/pickups?", -// params={ -// "_format": "json", -// "type": "", -// "start": "1622332800", -// "end": "163861328100" -// } -//} From 6af5368a872b414ceadb7816427dbc4fbeac7e1c Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 15:13:36 +0200 Subject: [PATCH 26/42] Error handler now returns a string instead (closes #10) Commit hook now properly returns status code on failed formatting --- .hooks/pre-commit | 5 ++++- src/catchers.rs | 6 ++++++ src/ivago/controller/pickup_times.rs | 14 ++++++++------ src/main.rs | 2 ++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 src/catchers.rs diff --git a/.hooks/pre-commit b/.hooks/pre-commit index a2dd254..825db47 100755 --- a/.hooks/pre-commit +++ b/.hooks/pre-commit @@ -1,7 +1,10 @@ #!/usr/bin/env bash # This hook lints the code, and if we're on develop or master, also forces the tests to pass. -cargo fmt -- --check +make lint &> /dev/null 2>&1 || { + >&2 echo "Format check failed, use 'make lint' for more information."; + exit 1; +} branch=`git rev-parse --abbrev-ref HEAD` diff --git a/src/catchers.rs b/src/catchers.rs new file mode 100644 index 0000000..7992450 --- /dev/null +++ b/src/catchers.rs @@ -0,0 +1,6 @@ +use rocket::Request; + +#[catch(404)] +pub fn not_found(_: &Request) -> String { + String::from("This route doesn't exist or doesn't use the specified parameters.") +} diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 202f8fc..2434713 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -14,33 +14,34 @@ 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. +/// 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 { - // Beautiful how this exact example is in the docs match BasicDate::try_from(form_value.as_str()) { Err(_) => Err(form_value), - // Here, we can assume these parses will work, because the regex - // didn't fail 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 = (); @@ -49,8 +50,6 @@ impl TryFrom<&str> for BasicDate { match re.captures(s) { None => Err(()), - // 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(), @@ -74,6 +73,9 @@ impl From for 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/main.rs b/src/main.rs index 882db7c..49101f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate rocket; // Route modules +mod catchers; mod errors; mod hello; mod ivago; @@ -12,6 +13,7 @@ fn rocket() -> rocket::Rocket { rocket::ignite() .mount("/hello", hello::routes()) .mount("/ivago", ivago::routes()) + .register(catchers![catchers::not_found]) } fn main() { From 001b7b324e8efebe3661315c8cc3235b18f3cb79 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 19:15:47 +0200 Subject: [PATCH 27/42] Removed unnecessary json return data (closes #12) --- src/ivago/controller/pickup_times.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 2434713..7e8d3fb 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -51,6 +51,7 @@ impl TryFrom<&str> for BasicDate { 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(), @@ -86,12 +87,9 @@ impl Serialize for PickupTime { where S: Serializer, { - let mut s = serializer.serialize_struct("PickupTime", 4)?; + let mut s = serializer.serialize_struct("PickupTime", 2)?; 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() } } @@ -101,8 +99,6 @@ impl Serialize for PickupTime { pub struct PickupTime { date: BasicDate, label: String, - classes: Vec, - url: String, } pub fn get_pickup_times( @@ -142,13 +138,6 @@ pub fn get_pickup_times( // TODO should I check here if the parsing worked? date: BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(), label: map.get("label").unwrap().to_string(), - classes: map - .get("classes") - .unwrap() - .split_whitespace() - .map(|x| String::from(x)) - .collect(), - url: map.get("url").unwrap().to_string(), }) } From c0311132ec2e2bb3f3ad8c2512a80917f1807dbc Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Sun, 4 Apr 2021 19:22:53 +0200 Subject: [PATCH 28/42] Started writing some tests (#5) --- src/ivago/tests.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ivago/tests.rs b/src/ivago/tests.rs index 8b13789..2b91e48 100644 --- a/src/ivago/tests.rs +++ b/src/ivago/tests.rs @@ -1 +1,26 @@ +use rocket::http::Status; +use rocket::local::Client; +fn rocket() -> rocket::Rocket { + rocket::ignite().mount("/", super::routes()) +} + +/// Test 404 response +#[test] +fn test_404_response() { + let client = Client::new(rocket()).expect("valid rocket instance"); + let response = client.get("/").dispatch(); + + assert_eq!(response.status(), Status::NotFound); +} + +/// Test 404 on invalid parameters +#[test] +fn test_invalid_parameters() { + let client = Client::new(rocket()).expect("valid rocket instance"); + let response = client + .get("/?street=astreet+(city)&number=500&start_date=2021-04-04&end_date=2021-04-555") + .dispatch(); + + assert_eq!(response.status(), Status::NotFound); +} From 89bd29dc78de762db56fea08f3e4f5c06fbcadc8 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 5 Apr 2021 09:59:40 +0200 Subject: [PATCH 29/42] [closes #2] removed hello world endpoints --- src/hello/controller.rs | 0 src/hello/mod.rs | 21 --------------------- src/hello/tests.rs | 24 ------------------------ src/main.rs | 2 -- 4 files changed, 47 deletions(-) delete mode 100644 src/hello/controller.rs delete mode 100644 src/hello/mod.rs delete mode 100644 src/hello/tests.rs diff --git a/src/hello/controller.rs b/src/hello/controller.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/hello/mod.rs b/src/hello/mod.rs deleted file mode 100644 index 608d234..0000000 --- a/src/hello/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[cfg(test)] -mod tests; - -pub fn routes() -> Vec { - routes![world, hello, name_age] -} - -#[get("/world")] -fn world() -> &'static str { - "Hello, world!" -} - -#[get("/")] -fn hello(name: String) -> String { - format!("Hello, {}", name) -} - -#[get("/world?&")] -fn name_age(name: String, age: u16) -> String { - format!("Hello, {} who is {} years old!", name, age) -} diff --git a/src/hello/tests.rs b/src/hello/tests.rs deleted file mode 100644 index e19264d..0000000 --- a/src/hello/tests.rs +++ /dev/null @@ -1,24 +0,0 @@ -use rocket::http::Status; -use rocket::local::Client; - -fn rocket() -> rocket::Rocket { - rocket::ignite().mount("/", super::routes()) -} - -#[test] -fn test_world() { - let client = Client::new(rocket()).expect("valid rocket instance"); - let mut response = client.get("/world").dispatch(); - - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body_string(), Some("Hello, world!".into())); -} - -#[test] -fn test_hello() { - let client = Client::new(rocket()).expect("valid rocket instance"); - let mut response = client.get("/thisisaname").dispatch(); - - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body_string(), Some("Hello, thisisaname".into())); -} diff --git a/src/main.rs b/src/main.rs index 49101f6..9452f9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,10 @@ extern crate rocket; // Route modules mod catchers; mod errors; -mod hello; mod ivago; fn rocket() -> rocket::Rocket { rocket::ignite() - .mount("/hello", hello::routes()) .mount("/ivago", ivago::routes()) .register(catchers![catchers::not_found]) } From ea72231612789d936b07000e4ff1676729413544 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 5 Apr 2021 10:48:10 +0200 Subject: [PATCH 30/42] [closes #8] modularized ivago endpoint module --- src/ivago/controller/mod.rs | 6 +- src/ivago/controller/pickup_times.rs | 96 +-------------------- src/ivago/controller/search.rs | 61 +------------ src/ivago/controller/structs/basic_date.rs | 76 ++++++++++++++++ src/ivago/controller/structs/mod.rs | 7 ++ src/ivago/controller/structs/pickup_time.rs | 21 +++++ src/ivago/controller/structs/street.rs | 61 +++++++++++++ src/ivago/mod.rs | 16 ++-- 8 files changed, 180 insertions(+), 164 deletions(-) create mode 100644 src/ivago/controller/structs/basic_date.rs create mode 100644 src/ivago/controller/structs/mod.rs create mode 100644 src/ivago/controller/structs/pickup_time.rs create mode 100644 src/ivago/controller/structs/street.rs 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, )?)) } From 8057f50f540443b5a012cacda4134d239e551fc6 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 5 Apr 2021 11:00:41 +0200 Subject: [PATCH 31/42] Removed unnecessary String cloning --- src/errors.rs | 5 ++--- src/ivago/controller/search.rs | 2 +- src/ivago/controller/structs/street.rs | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 6403235..cf78244 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,4 +1,3 @@ -use reqwest::Error; use rocket::http::Status; pub enum FejError { @@ -16,8 +15,8 @@ impl From for Status { } // TODO make this more advanced where possible -impl From for FejError { - fn from(_: Error) -> FejError { +impl From for FejError { + fn from(_: reqwest::Error) -> FejError { FejError::FailedRequest } } diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 8f5d8ff..68c1d1f 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -24,7 +24,7 @@ pub fn search_streets(street_name: &String) -> Result, FejError> { // 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.clone()) { + match Street::try_from(value) { Ok(street) => output.push(street), Err(_) => continue, } diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/structs/street.rs index 257d694..e26c3a0 100644 --- a/src/ivago/controller/structs/street.rs +++ b/src/ivago/controller/structs/street.rs @@ -28,10 +28,10 @@ impl Serialize for Street { } } -impl TryFrom for Street { +impl TryFrom<&String> for Street { type Error = (); - fn try_from(value: String) -> Result { + fn try_from(value: &String) -> Result { if let Some(index) = value.find('(') { Ok(Street { name: (value[0..index - 1].trim()).to_string(), From e7bd93c3a4d25ddff1874a57df37f23ae9d90ec4 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 5 Apr 2021 11:10:48 +0200 Subject: [PATCH 32/42] Switched another function call to &str --- src/ivago/controller/search.rs | 4 ++-- src/ivago/controller/structs/street.rs | 4 ++-- src/ivago/mod.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 68c1d1f..8c84113 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -14,7 +14,7 @@ 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: &String) -> Result, FejError> { +pub fn search_streets(street_name: &str) -> Result, FejError> { let client = reqwest::Client::new(); let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; let data: Vec> = response.json()?; @@ -24,7 +24,7 @@ pub fn search_streets(street_name: &String) -> Result, FejError> { // 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) { + match Street::try_from(value.as_str()) { Ok(street) => output.push(street), Err(_) => continue, } diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/structs/street.rs index e26c3a0..35f7e16 100644 --- a/src/ivago/controller/structs/street.rs +++ b/src/ivago/controller/structs/street.rs @@ -28,10 +28,10 @@ impl Serialize for Street { } } -impl TryFrom<&String> for Street { +impl TryFrom<&str> for Street { type Error = (); - fn try_from(value: &String) -> Result { + fn try_from(value: &str) -> Result { if let Some(index) = value.find('(') { Ok(Street { name: (value[0..index - 1].trim()).to_string(), diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index cc916d0..78b1fc2 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -13,7 +13,7 @@ pub fn routes() -> Vec { #[get("/search?")] pub fn route_search_streets(street: String) -> Result>, Status> { - Ok(Json(search_streets(&street)?)) + Ok(Json(search_streets(street.as_str())?)) } #[get("/?&&&")] From 5dd2b3a8786aa4d61a012345e506fa7a2abfaa8b Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 5 Apr 2021 12:20:55 +0200 Subject: [PATCH 33/42] Stopped exposing PickupTime fields --- src/ivago/controller/pickup_times.rs | 12 ++++----- src/ivago/controller/structs/basic_date.rs | 27 +++++++++++---------- src/ivago/controller/structs/pickup_time.rs | 23 ++++++++++++------ 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 3727117..0a9bd3d 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -31,8 +31,8 @@ pub fn get_pickup_times( .query(&[ ("_format", "json"), ("type", ""), - ("start", &i64::from(start_date).to_string()), - ("end", &i64::from(end_date).to_string()), + ("start", &start_date.epoch().to_string()), + ("end", &end_date.epoch().to_string()), ]) .send()?; let data: Vec> = response.json()?; @@ -40,11 +40,11 @@ pub fn get_pickup_times( let mut output: Vec = Vec::new(); for map in data.iter() { - output.push(PickupTime { + output.push(PickupTime::new( // TODO should I check here if the parsing worked? - date: BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(), - label: map.get("label").unwrap().to_string(), - }) + BasicDate::try_from(map.get("date").unwrap().as_str()).unwrap(), + map.get("label").unwrap().to_string(), + )) } Ok(output) diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs index 8669954..421d7df 100644 --- a/src/ivago/controller/structs/basic_date.rs +++ b/src/ivago/controller/structs/basic_date.rs @@ -15,6 +15,20 @@ pub struct BasicDate { 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 { type Error = &'v RawStr; @@ -61,16 +75,3 @@ impl Serialize for BasicDate { 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/pickup_time.rs b/src/ivago/controller/structs/pickup_time.rs index 78d7e8e..6f2249d 100644 --- a/src/ivago/controller/structs/pickup_time.rs +++ b/src/ivago/controller/structs/pickup_time.rs @@ -1,6 +1,22 @@ use super::BasicDate; use serde::ser::{Serialize, SerializeStruct, Serializer}; +/// Represents a pickup time instance. All fields are a direct map of the +/// original API +pub struct PickupTime { + date: BasicDate, + label: String, +} + +impl PickupTime { + pub fn new(date: BasicDate, label: String) -> PickupTime { + PickupTime { + date: date, + label: label, + } + } +} + impl Serialize for PickupTime { fn serialize(&self, serializer: S) -> Result where @@ -12,10 +28,3 @@ impl Serialize for PickupTime { 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, -} From dd1efaa34dfc4fbd4dcc3825035a52d88bb86393 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 10:11:15 +0200 Subject: [PATCH 34/42] [#14] BasicDate now relies on DateTime for its parsing --- Cargo.lock | 20 +++++++ Cargo.toml | 1 + src/errors.rs | 7 +++ src/ivago/controller/pickup_times.rs | 19 +++--- src/ivago/controller/structs/basic_date.rs | 69 ++++++++-------------- src/ivago/controller/structs/street.rs | 4 +- src/ivago/mod.rs | 5 +- src/main.rs | 2 + 8 files changed, 71 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c0d5ae..5b96436 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,16 @@ 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" @@ -340,6 +350,7 @@ name = "fej" version = "0.0.1" dependencies = [ "chrono", + "chrono-tz", "regex", "reqwest", "rocket", @@ -1010,6 +1021,15 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 6969693..3e94794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" rocket = "0.4.7" serde = "1.0.124" chrono = "0.4.19" +chrono-tz = "0.5.3" regex = "1.4.5" [dependencies.reqwest] diff --git a/src/errors.rs b/src/errors.rs index cf78244..a34dba6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,4 @@ +// I can probably do this way easier using an external crate, I should do that use rocket::http::Status; pub enum FejError { @@ -20,3 +21,9 @@ impl From for FejError { FejError::FailedRequest } } + +impl From for FejError { + fn from(_: chrono::ParseError) -> FejError { + FejError::InvalidArgument + } +} diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 0a9bd3d..955e840 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -1,5 +1,7 @@ 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}; @@ -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"; pub fn get_pickup_times( - street: Street, - number: u32, - start_date: BasicDate, - end_date: BasicDate, + street: &Street, + number: &u32, + start_date: &DateTime, + end_date: &DateTime, ) -> Result, FejError> { let client = reqwest::Client::builder().cookie_store(true).build()?; @@ -31,8 +33,8 @@ pub fn get_pickup_times( .query(&[ ("_format", "json"), ("type", ""), - ("start", &start_date.epoch().to_string()), - ("end", &end_date.epoch().to_string()), + ("start", &start_date.timestamp().to_string()), + ("end", &end_date.timestamp().to_string()), ]) .send()?; let data: Vec> = response.json()?; @@ -41,8 +43,9 @@ pub fn get_pickup_times( 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(), + // TODO it's really not logical here that this would return an + // "InvalidArgument" error, it should just skip the item + BasicDate::try_from(map.get("date").unwrap().as_str())?, map.get("label").unwrap().to_string(), )) } diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs index 421d7df..e7518fa 100644 --- a/src/ivago/controller/structs/basic_date.rs +++ b/src/ivago/controller/structs/basic_date.rs @@ -1,33 +1,16 @@ -use chrono::{FixedOffset, TimeZone}; -use regex::Regex; +use crate::errors::FejError; +use chrono::{DateTime, NaiveDate, TimeZone}; +use chrono_tz::Europe::Brussels; +use chrono_tz::Tz; 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, -} - -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 class is a simple wrapper around chrono's DateTime. Its sole purpose +/// is to avoid error E0117. +pub struct BasicDate(pub DateTime); /// This allows us to use BasicDate as a query parameter in our routes. impl<'v> FromFormValue<'v> for BasicDate { @@ -41,29 +24,25 @@ 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. +impl TryFrom<&str> for BasicDate { + type Error = FejError; + + fn try_from(s: &str) -> Result { + 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)?, + )) } } -/// 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 From<&BasicDate> for String { + fn from(date: &BasicDate) -> String { + format!("{}", date.0.format("%Y-%m-%d")) } } @@ -72,6 +51,6 @@ impl Serialize for BasicDate { where S: Serializer, { - serializer.serialize_str(&self.to_string()) + serializer.serialize_str(&String::from(self)) } } diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/structs/street.rs index 35f7e16..1ca64ec 100644 --- a/src/ivago/controller/structs/street.rs +++ b/src/ivago/controller/structs/street.rs @@ -10,8 +10,8 @@ pub struct Street { pub city: String, } -impl From for String { - fn from(street: Street) -> String { +impl From<&Street> for String { + fn from(street: &Street) -> String { format!("{} ({})", street.name, street.city) } } diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 78b1fc2..4a03e48 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -24,6 +24,9 @@ pub fn route_get_pickup_times( end_date: BasicDate, ) -> Result>, Status> { Ok(Json(get_pickup_times( - street, number, start_date, end_date, + &street, + &number, + &start_date.0, + &end_date.0, )?)) } diff --git a/src/main.rs b/src/main.rs index 9452f9f..485f370 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ #[macro_use] extern crate rocket; +extern crate chrono_tz; + // Route modules mod catchers; mod errors; From e78de73d83ee224899d1e2443d301e7dbcb30179 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 10:56:35 +0200 Subject: [PATCH 35/42] [#14] Added fancy loops to pickup_times & search --- src/ivago/controller/pickup_times.rs | 15 ++++++++------- src/ivago/controller/search.rs | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index 955e840..d2721f9 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -41,13 +41,14 @@ pub fn get_pickup_times( let mut output: Vec = Vec::new(); - for map in data.iter() { - output.push(PickupTime::new( - // TODO it's really not logical here that this would return an - // "InvalidArgument" error, it should just skip the item - BasicDate::try_from(map.get("date").unwrap().as_str())?, - map.get("label").unwrap().to_string(), - )) + 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())) + } } Ok(output) diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs index 8c84113..e66652d 100644 --- a/src/ivago/controller/search.rs +++ b/src/ivago/controller/search.rs @@ -13,23 +13,17 @@ 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, FejError> { let client = reqwest::Client::new(); let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; let data: Vec> = response.json()?; - let mut output: Vec = 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) + // 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()) } From 2e73d88ae9bdb207b3340ab6bf1f5a4e680c15d0 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 22:39:04 +0200 Subject: [PATCH 36/42] [#13] Further overhaul of the project structure for better testing --- Cargo.toml | 15 ++++++++++++++- Dockerfile | 6 ++++-- Makefile | 4 ++-- benches/ivago.rs | 1 + src/errors.rs | 1 + src/ivago/controller/structs/basic_date.rs | 13 +++++++++++++ src/ivago/mod.rs | 2 -- src/lib.rs | 11 +++++++++++ src/main.rs | 9 +-------- src/ivago/tests.rs => tests/ivago.rs | 2 +- 10 files changed, 48 insertions(+), 16 deletions(-) create mode 100644 benches/ivago.rs create mode 100644 src/lib.rs rename src/ivago/tests.rs => tests/ivago.rs (91%) diff --git a/Cargo.toml b/Cargo.toml index 3e94794..0ebc525 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,21 @@ version = "0.0.1" authors = ["Jef Roosens "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[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 + [dependencies] rocket = "0.4.7" serde = "1.0.124" diff --git a/Dockerfile b/Dockerfile index 4a3ed8b..457e805 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,11 +13,13 @@ RUN apk update && apk add --no-cache openssl-dev build-base curl && \ COPY Cargo.toml Cargo.lock ./ COPY src/ ./src/ -# Finally, build the project +# Run the tests, don't want no broken docker image +# And then 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 build --release +RUN RUSTFLAGS="-C target-feature=-crt-static" cargo test && \ + RUSTFLAGS="-C target-feature=-crt-static" cargo build --release --bin fej # Now, we create the actual image diff --git a/Makefile b/Makefile index a1baf59..ab50282 100644 --- a/Makefile +++ b/Makefile @@ -23,13 +23,13 @@ push: # Run run: - @ RUST_BACKTRACE=1 cargo run + @ RUST_BACKTRACE=1 cargo run --bin fej .PHONY: run # Testing test: - @ cargo test + @ cargo test --no-fail-fast .PHONY: test format: diff --git a/benches/ivago.rs b/benches/ivago.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/benches/ivago.rs @@ -0,0 +1 @@ + diff --git a/src/errors.rs b/src/errors.rs index a34dba6..dfb137f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ // 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, diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs index e7518fa..97ef3e4 100644 --- a/src/ivago/controller/structs/basic_date.rs +++ b/src/ivago/controller/structs/basic_date.rs @@ -10,6 +10,7 @@ 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); /// This allows us to use BasicDate as a query parameter in our routes. @@ -54,3 +55,15 @@ impl Serialize for BasicDate { 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)); + } +} diff --git a/src/ivago/mod.rs b/src/ivago/mod.rs index 4a03e48..28ac98b 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -1,6 +1,4 @@ mod controller; -#[cfg(test)] -mod tests; use controller::structs::{BasicDate, PickupTime, Street}; use controller::{get_pickup_times, search_streets}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b1e2733 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] +extern crate rocket; + +// Route modules +pub mod ivago; + +// Helper modules +pub mod catchers; +pub mod errors; diff --git a/src/main.rs b/src/main.rs index 485f370..ad86d98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,7 @@ -#![feature(proc_macro_hygiene, decl_macro)] - #[macro_use] extern crate rocket; -extern crate chrono_tz; - -// Route modules -mod catchers; -mod errors; -mod ivago; +use fej_lib::{catchers, ivago}; fn rocket() -> rocket::Rocket { rocket::ignite() diff --git a/src/ivago/tests.rs b/tests/ivago.rs similarity index 91% rename from src/ivago/tests.rs rename to tests/ivago.rs index 2b91e48..619b9c7 100644 --- a/src/ivago/tests.rs +++ b/tests/ivago.rs @@ -2,7 +2,7 @@ use rocket::http::Status; use rocket::local::Client; fn rocket() -> rocket::Rocket { - rocket::ignite().mount("/", super::routes()) + rocket::ignite().mount("/", fej_lib::ivago::routes()) } /// Test 404 response From e4b8be3ba3bfd0a978bdb2bc2746943948a9f4b8 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 22:53:29 +0200 Subject: [PATCH 37/42] [#13] added some more unit tests, hated life for a bit --- src/ivago/controller/structs/basic_date.rs | 1 + src/ivago/controller/structs/pickup_time.rs | 2 ++ src/ivago/controller/structs/street.rs | 28 +++++++++++++++++++-- tests/ivago.rs | 3 +++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs index 97ef3e4..7468da5 100644 --- a/src/ivago/controller/structs/basic_date.rs +++ b/src/ivago/controller/structs/basic_date.rs @@ -60,6 +60,7 @@ impl Serialize for BasicDate { mod tests { use super::*; + /// Tests catching a string with an invalid date #[test] fn test_invalid_date() { let val = "2012-13-12"; diff --git a/src/ivago/controller/structs/pickup_time.rs b/src/ivago/controller/structs/pickup_time.rs index 6f2249d..33718db 100644 --- a/src/ivago/controller/structs/pickup_time.rs +++ b/src/ivago/controller/structs/pickup_time.rs @@ -28,3 +28,5 @@ impl Serialize for PickupTime { s.end() } } + +// I'd put tests here, but there's barely anything to do diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/structs/street.rs index 1ca64ec..00f4de4 100644 --- a/src/ivago/controller/structs/street.rs +++ b/src/ivago/controller/structs/street.rs @@ -6,8 +6,19 @@ use std::convert::TryFrom; /// Represents a street pub struct Street { - pub name: String, - pub city: String, + name: String, + city: String, +} + +impl Street { + // This constructor just makes my life a bit easier during testing + #[cfg(test)] + fn new(name: String, city: String) -> Street { + Street { + name: name, + city: city, + } + } } impl From<&Street> for String { @@ -50,6 +61,7 @@ impl<'v> FromFormValue<'v> for Street { // 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 { @@ -59,3 +71,15 @@ impl<'v> FromFormValue<'v> for Street { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_to_string() { + let street = Street::new(String::from("testname"), String::from("city")); + + assert_eq!(String::from("testname (city)"), String::from(&street)); + } +} diff --git a/tests/ivago.rs b/tests/ivago.rs index 619b9c7..d78501a 100644 --- a/tests/ivago.rs +++ b/tests/ivago.rs @@ -1,3 +1,4 @@ +/// In here, any non-unit tests are placed. use rocket::http::Status; use rocket::local::Client; @@ -12,9 +13,11 @@ fn test_404_response() { let response = client.get("/").dispatch(); assert_eq!(response.status(), Status::NotFound); + // TODO add text check as well } /// Test 404 on invalid parameters +// TODO make this check a 400 instead #[test] fn test_invalid_parameters() { let client = Client::new(rocket()).expect("valid rocket instance"); From edc3605770847bd971fffb8acc9de4d0685cbf8b Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 23:00:54 +0200 Subject: [PATCH 38/42] [#13] small changes --- src/ivago/controller/pickup_times.rs | 3 ++- src/ivago/controller/structs/basic_date.rs | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs index d2721f9..95d080c 100644 --- a/src/ivago/controller/pickup_times.rs +++ b/src/ivago/controller/pickup_times.rs @@ -45,7 +45,8 @@ pub fn get_pickup_times( .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 + // Because we filtered the maps in the loop, we can safely use 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())) } diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/structs/basic_date.rs index 7468da5..38c6f6c 100644 --- a/src/ivago/controller/structs/basic_date.rs +++ b/src/ivago/controller/structs/basic_date.rs @@ -13,7 +13,6 @@ use std::convert::TryFrom; #[derive(Debug, PartialEq)] pub struct BasicDate(pub DateTime); -/// This allows us to use BasicDate as a query parameter in our routes. impl<'v> FromFormValue<'v> for BasicDate { type Error = &'v RawStr; @@ -25,7 +24,6 @@ impl<'v> FromFormValue<'v> for BasicDate { } } -/// This is used to serialize BasicDate. impl TryFrom<&str> for BasicDate { type Error = FejError; From 4950c3660e0f0ee6745206adec38e9c2ac8acae0 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 23:29:45 +0200 Subject: [PATCH 39/42] [#13] simplified ivago controller module --- .../controller/{structs => }/basic_date.rs | 0 src/ivago/controller/mod.rs | 100 +++++++++++++++++- .../controller/{structs => }/pickup_time.rs | 0 src/ivago/controller/pickup_times.rs | 56 ---------- src/ivago/controller/search.rs | 29 ----- src/ivago/controller/{structs => }/street.rs | 0 src/ivago/controller/structs/mod.rs | 7 -- src/ivago/mod.rs | 2 +- 8 files changed, 96 insertions(+), 98 deletions(-) rename src/ivago/controller/{structs => }/basic_date.rs (100%) rename src/ivago/controller/{structs => }/pickup_time.rs (100%) delete mode 100644 src/ivago/controller/pickup_times.rs delete mode 100644 src/ivago/controller/search.rs rename src/ivago/controller/{structs => }/street.rs (100%) delete mode 100644 src/ivago/controller/structs/mod.rs diff --git a/src/ivago/controller/structs/basic_date.rs b/src/ivago/controller/basic_date.rs similarity index 100% rename from src/ivago/controller/structs/basic_date.rs rename to src/ivago/controller/basic_date.rs diff --git a/src/ivago/controller/mod.rs b/src/ivago/controller/mod.rs index fddd7e4..0084a3d 100644 --- a/src/ivago/controller/mod.rs +++ b/src/ivago/controller/mod.rs @@ -1,6 +1,96 @@ -mod pickup_times; -mod search; -pub mod structs; +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}; -pub use pickup_times::get_pickup_times; -pub use search::search_streets; +mod basic_date; +mod pickup_time; +mod street; + +pub use basic_date::BasicDate; +pub use pickup_time::PickupTime; +pub use street::Street; + +/// Endpoint for the search feature +const SEARCH_URL: &str = "https://www.ivago.be/nl/particulier/autocomplete/garbage/streets"; +/// Endpoint for populating the initial cookies +const BASE_URL: &str = "https://www.ivago.be/nl/particulier/afval/ophaling"; +/// Endpoint for the actual calendar output +const CAL_URL: &str = "https://www.ivago.be/nl/particulier/garbage/pick-up/pickups"; + +/// Searches the Ivago API for streets in the given city +/// +/// # Arguments +/// +/// * `street` - name of the street +/// * `city` - city the street is in +pub fn search_streets(street_name: &str) -> Result, FejError> { + let client = reqwest::Client::new(); + let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; + let data: Vec> = 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()) +} + +/// Returns the pickup times for the various trash types +/// +/// # Arguments +/// +/// * `street` - desired street +/// * `number` - house number +/// * `start_date` - earliest date for the results +/// * `end_date` - latest date for the results +pub fn get_pickup_times( + street: &Street, + number: &u32, + start_date: &DateTime, + end_date: &DateTime, +) -> Result, FejError> { + let client = reqwest::Client::builder().cookie_store(true).build()?; + + // This populates the cookies with the necessary values + client + .post(BASE_URL) + .form(&[ + ("garbage_type", ""), + ("ivago_street", &String::from(street)), + ("number", &number.to_string()), + ("form_id", "garbage_address_form"), + ]) + .send()?; + + let response = client + .get(CAL_URL) + .query(&[ + ("_format", "json"), + ("type", ""), + ("start", &start_date.timestamp().to_string()), + ("end", &end_date.timestamp().to_string()), + ]) + .send()?; + let data: Vec> = response.json()?; + + let mut output: Vec = 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 use 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())) + } + } + + Ok(output) +} diff --git a/src/ivago/controller/structs/pickup_time.rs b/src/ivago/controller/pickup_time.rs similarity index 100% rename from src/ivago/controller/structs/pickup_time.rs rename to src/ivago/controller/pickup_time.rs diff --git a/src/ivago/controller/pickup_times.rs b/src/ivago/controller/pickup_times.rs deleted file mode 100644 index 95d080c..0000000 --- a/src/ivago/controller/pickup_times.rs +++ /dev/null @@ -1,56 +0,0 @@ -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}; - -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, - end_date: &DateTime, -) -> Result, FejError> { - let client = reqwest::Client::builder().cookie_store(true).build()?; - - // This populates the cookies with the necessary values - client - .post(BASE_URL) - .form(&[ - ("garbage_type", ""), - ("ivago_street", &String::from(street)), - ("number", &number.to_string()), - ("form_id", "garbage_address_form"), - ]) - .send()?; - - let response = client - .get(CAL_URL) - .query(&[ - ("_format", "json"), - ("type", ""), - ("start", &start_date.timestamp().to_string()), - ("end", &end_date.timestamp().to_string()), - ]) - .send()?; - let data: Vec> = response.json()?; - - let mut output: Vec = 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 use 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())) - } - } - - Ok(output) -} diff --git a/src/ivago/controller/search.rs b/src/ivago/controller/search.rs deleted file mode 100644 index e66652d..0000000 --- a/src/ivago/controller/search.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::structs::Street; -use crate::errors::FejError; -use reqwest::blocking as reqwest; -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"; - -/// Searches the Ivago API for streets in the given city -/// -/// # Arguments -/// -/// * `street` - name of the street -/// * `city` - city the street is in -pub fn search_streets(street_name: &str) -> Result, FejError> { - let client = reqwest::Client::new(); - let response = client.get(SEARCH_URL).query(&[("q", street_name)]).send()?; - let data: Vec> = 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()) -} diff --git a/src/ivago/controller/structs/street.rs b/src/ivago/controller/street.rs similarity index 100% rename from src/ivago/controller/structs/street.rs rename to src/ivago/controller/street.rs diff --git a/src/ivago/controller/structs/mod.rs b/src/ivago/controller/structs/mod.rs deleted file mode 100644 index ed6cf3c..0000000 --- a/src/ivago/controller/structs/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -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/mod.rs b/src/ivago/mod.rs index 28ac98b..e4d1037 100644 --- a/src/ivago/mod.rs +++ b/src/ivago/mod.rs @@ -1,7 +1,7 @@ mod controller; -use controller::structs::{BasicDate, PickupTime, Street}; use controller::{get_pickup_times, search_streets}; +use controller::{BasicDate, PickupTime, Street}; use rocket::http::Status; use rocket_contrib::json::Json; From 7b9bf223c66def59307b819a33ea47cd3756913a Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Thu, 8 Apr 2021 23:56:08 +0200 Subject: [PATCH 40/42] [#16] tried changing dockerfile to buildkit (WIP) --- Dockerfile | 8 +++++--- build | 2 +- src/ivago/README.md | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 457e805..48008ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# syntax = docker/dockerfile:1.2 + # We use a multi-stage build to end up with a very small final image FROM alpine:latest AS builder @@ -18,8 +20,8 @@ COPY src/ ./src/ # 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 --mount=type=cache,target=/usr/src/app/target RUSTFLAGS="-C target-feature=-crt-static" cargo test && \ + RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --bin fej --root /usr/local/bin # Now, we create the actual image @@ -29,6 +31,6 @@ FROM alpine:latest RUN apk update && apk add --no-cache openssl libgcc # Copy binary over to final image -COPY --from=builder /usr/src/app/target/release/fej /usr/local/bin/fej +COPY --from=builder /usr/local/bin/fej /usr/local/bin/fej CMD ["/usr/local/bin/fej"] diff --git a/build b/build index 0117f6a..8abbc39 100755 --- a/build +++ b/build @@ -26,7 +26,7 @@ else fi # Run the actual build command -docker build -t "$1:$tags" . +DOCKER_BUILDKIT=1 docker build -t "$1:$tags" . if [[ "$2" = push ]]; then [[ "$branch" =~ ^develop|master$ ]] || { diff --git a/src/ivago/README.md b/src/ivago/README.md index c69c1b4..02d2d0e 100644 --- a/src/ivago/README.md +++ b/src/ivago/README.md @@ -1,5 +1,5 @@ # Ivago This part of the API is a wrapper around the Ivago website (Ivago being the -company that collects the trash in my city). Their city isn't exactly RESTful, +company that collects the trash in my city). Their site isn't exactly RESTful, so this endpoint simply wraps it in a RESTful wrapper. From 8da8af9a45166b0240b74b11c0c5f2c3216ff028 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 12 Apr 2021 15:43:23 +0200 Subject: [PATCH 41/42] [closes #16] fixed Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 48008ca..c54bb86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,9 @@ COPY src/ ./src/ # 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 +# NOTE: cargo install auto-appends bin to the path RUN --mount=type=cache,target=/usr/src/app/target RUSTFLAGS="-C target-feature=-crt-static" cargo test && \ - RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --bin fej --root /usr/local/bin + RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --bin fej --root /usr/local # Now, we create the actual image From 7243be302c773028c63e4cf069d8bf59a97012df Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 12 Apr 2021 15:46:03 +0200 Subject: [PATCH 42/42] Bumped version to 0.1.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b96436..532c627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,7 +347,7 @@ dependencies = [ [[package]] name = "fej" -version = "0.0.1" +version = "0.1.0" dependencies = [ "chrono", "chrono-tz", diff --git a/Cargo.toml b/Cargo.toml index 0ebc525..97796ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fej" -version = "0.0.1" +version = "0.1.0" authors = ["Jef Roosens "] edition = "2018"