diff --git a/Cargo.lock b/Cargo.lock index 84f7e73..acb1047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,25 +29,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "assert-json-diff" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "assert_cmd" version = "2.0.2" @@ -140,15 +121,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-object-pool" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" -dependencies = [ - "async-std", -] - [[package]] name = "async-process" version = "1.2.0" @@ -200,17 +172,6 @@ version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" -[[package]] -name = "async-trait" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -249,32 +210,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "basic-cookies" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" -dependencies = [ - "lalrpop", - "lalrpop-util", - "regex", -] - -[[package]] -name = "bit-set" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" version = "1.3.2" @@ -389,12 +324,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" -[[package]] -name = "castaway" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed247d1586918e46f2bbe0f13b06498db8dab5a8c1093f156652e9f2e0a73fc3" - [[package]] name = "cc" version = "1.0.71" @@ -480,12 +409,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "ctor" version = "0.1.21" @@ -496,37 +419,6 @@ dependencies = [ "syn", ] -[[package]] -name = "curl" -version = "0.4.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaa3b8db7f3341ddef15786d250106334d4a6c4b0ae4a46cd77082777d9849b9" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.49+curl-7.79.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f44960aea24a786a46907b8824ebc0e66ca06bf4e4978408c7499620343483" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - [[package]] name = "date_time_parser" version = "0.1.1" @@ -554,12 +446,6 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - [[package]] name = "difflib" version = "0.4.0" @@ -594,16 +480,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -627,15 +503,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "ena" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" -dependencies = [ - "log", -] - [[package]] name = "encoding_rs" version = "0.8.29" @@ -666,12 +533,6 @@ dependencies = [ "instant", ] -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "fnv" version = "1.0.7" @@ -944,34 +805,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" -[[package]] -name = "httpmock" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "218c2c3dabf4497c87d042b43984a5fd03bcd8a6adb0056ade7df20f88df225a" -dependencies = [ - "assert-json-diff", - "async-object-pool", - "async-trait", - "base64 0.13.0", - "basic-cookies", - "crossbeam-utils", - "difference", - "form_urlencoded", - "futures-util", - "hyper", - "isahc", - "lazy_static", - "levenshtein", - "log", - "qstring", - "regex", - "serde", - "serde_json", - "serde_regex", - "tokio", -] - [[package]] name = "hyper" version = "0.14.14" @@ -1051,33 +884,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" -[[package]] -name = "isahc" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ef5402b1791c9fc479ef9871601a2f10e4cc0f14414a5c9c6e043fb51e5a56" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.10.1" @@ -1111,87 +917,18 @@ dependencies = [ "log", ] -[[package]] -name = "lalrpop" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" -dependencies = [ - "ascii-canvas", - "atty", - "bit-set", - "diff", - "ena", - "itertools", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" -dependencies = [ - "regex", -] - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - [[package]] name = "libc" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "lock_api" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.14" @@ -1242,7 +979,6 @@ dependencies = [ "chrono", "date_time_parser", "directories-next", - "httpmock", "itertools", "lazy_static", "num_enum", @@ -1312,12 +1048,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - [[package]] name = "ntapi" version = "0.3.6" @@ -1453,82 +1183,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" - -[[package]] -name = "pin-project" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.7" @@ -1566,12 +1226,6 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "predicates" version = "2.0.3" @@ -1666,15 +1320,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" -dependencies = [ - "percent-encoding", -] - [[package]] name = "quote" version = "1.0.10" @@ -1824,12 +1469,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "rustversion" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" - [[package]] name = "ryu" version = "1.0.5" @@ -1855,12 +1494,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "security-framework" version = "2.4.2" @@ -1924,16 +1557,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.0" @@ -2024,29 +1647,12 @@ dependencies = [ "libc", ] -[[package]] -name = "siphasher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" - [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.7.0" @@ -2084,19 +1690,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "string_cache" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.8.0" @@ -2189,17 +1782,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - [[package]] name = "term_size" version = "0.3.2" @@ -2286,15 +1868,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinyvec" version = "1.5.0" @@ -2322,24 +1895,10 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", - "signal-hook-registry", - "tokio-macros", "winapi", ] -[[package]] -name = "tokio-macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio-native-tls" version = "0.3.0" @@ -2386,7 +1945,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if", - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2412,16 +1970,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 3910f62..d1971c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,5 @@ itertools = "0.10" [dev-dependencies] temp-dir = "0.1" -httpmock = "0.6" pretty_assertions = "1.0" assert_cmd = "2.0" diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 183a3a6..2ad5fef 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -31,9 +31,8 @@ use cacache::Metadata; use chrono::{Duration, TimeZone}; use lazy_static::lazy_static; -use regex::Regex; -use reqwest::{blocking::Response, StatusCode, Url}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use reqwest::{StatusCode, Url}; +use serde::de::DeserializeOwned; use tracing::{info, warn}; mod fetchable; @@ -45,30 +44,13 @@ pub use fetchable::Fetchable; pub use wrapper::clear_cache as clear; use crate::{ - config::CONF, error::{Error, Result, ResultExt}, + request::{Api, DefaultApi, Headers, Response}, }; /// Returned by most functions in this module. type TextAndHeaders = (String, Headers); -lazy_static! { - /// Regex to find the next page in a link header - /// Probably only applicable to the current version of the openmensa API. - // TODO: Improve this. How do these LINK headers look in general? - static ref LINK_NEXT_PAGE_RE: Regex = Regex::new(r#"<([^>]*)>; rel="next""#).unwrap(); -} - -/// Assortment of headers relevant to the program. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[non_exhaustive] -pub struct Headers { - pub etag: Option, - pub this_page: Option, - pub next_page: Option, - pub last_page: Option, -} - /// Possible results from a cache load. #[derive(Debug, PartialEq)] enum CacheResult { @@ -175,32 +157,26 @@ fn get_and_update_cache( etag: Option, meta: Option, ) -> Result { - // Construct the request - let mut builder = CONF.client.get(url); - // Add If-None-Match header, if etag is present - if let Some(etag) = etag { - let etag_key = reqwest::header::IF_NONE_MATCH; - builder = builder.header(etag_key, etag); + lazy_static! { + static ref API: DefaultApi = DefaultApi::create().expect("Failed to create API"); } - let resp = wrapper::send_request(builder)?; - let status = resp.status(); - info!("Request to {:?} returned {}", url, status); + // Send request with optional ETag header + let resp = API.get(url, etag)?; + info!("Request to {:?} returned {}", url, resp.status); match meta { - Some(meta) if status == StatusCode::NOT_MODIFIED => { + Some(meta) if resp.status == StatusCode::NOT_MODIFIED => { // If we received code 304 NOT MODIFIED (after adding the If-None-Match) // our cache is actually fresh and it's timestamp should be updated - let headers = resp.headers().clone().into(); - // Just verified, that meta can be unwrapped! - touch_and_load_cache(url, &meta, headers) + touch_and_load_cache(url, &meta, resp.headers) } - _ if status.is_success() => { + _ if resp.status.is_success() => { // Request returned successfully, now update the cache with that update_cache_from_response(resp) } _ => { // Some error occured, just error out // TODO: Retrying would be an option - Err(Error::NonSuccessStatusCode(url.to_string(), resp.status())) + Err(Error::NonSuccessStatusCode(url.to_string(), resp.status)) } } } @@ -209,11 +185,9 @@ fn get_and_update_cache( /// /// Only relevant headers will be kept. fn update_cache_from_response(resp: Response) -> Result { - let headers: Headers = resp.headers().clone().into(); - let url = resp.url().as_str().to_owned(); - let text = resp.text().map_err(Error::Reqwest)?; - wrapper::write_cache(&headers, &url, &text)?; - Ok((text, headers)) + let url = resp.url.to_owned(); + wrapper::write_cache(&resp.headers, &url, &resp.body)?; + Ok((resp.body, resp.headers)) } /// Reset the cache's TTL, load and return it. @@ -248,44 +222,3 @@ fn to_text_and_headers(raw: Vec, meta: &serde_json::Value) -> Result for Headers { - fn from(map: reqwest::header::HeaderMap) -> Self { - use reqwest::header::*; - let etag = map - .get(ETAG) - .map(|raw| { - let utf8 = raw.to_str().ok()?; - Some(utf8.to_string()) - }) - .flatten(); - let this_page = map - .get("x-current-page") - .map(|raw| { - let utf8 = raw.to_str().ok()?; - utf8.parse().ok() - }) - .flatten(); - let next_page = map - .get(LINK) - .map(|raw| { - let utf8 = raw.to_str().ok()?; - let captures = LINK_NEXT_PAGE_RE.captures(utf8)?; - Some(captures[1].to_owned()) - }) - .flatten(); - let last_page = map - .get("x-total-pages") - .map(|raw| { - let utf8 = raw.to_str().ok()?; - utf8.parse().ok() - }) - .flatten(); - Self { - etag, - this_page, - last_page, - next_page, - } - } -} diff --git a/src/cache/tests.rs b/src/cache/tests.rs index a3452c9..b380b7a 100644 --- a/src/cache/tests.rs +++ b/src/cache/tests.rs @@ -1,6 +1,6 @@ use std::thread; -use httpmock::{Method::GET, MockServer}; +use lazy_static::lazy_static; use pretty_assertions::assert_eq; use super::*; @@ -33,39 +33,31 @@ fn test_cache_is_empty() { #[test] fn basic_caching() { - // === Setup === - let server = MockServer::start(); - server.mock(|when, then| { - when.method(GET).path("/test"); - then.status(200) - .header("ETag", "static") - .body("This page works!"); - }); - + let url = "http://invalid.local/test"; // Cache is empty - let val = try_load_cache(&server.url("/test"), Duration::max_value()).unwrap(); + let val = try_load_cache(url, Duration::max_value()).unwrap(); print_cache_list("After first read"); assert_eq!(val, CacheResult::Miss); // Populate the cache with the first request - let val = fetch(server.url("/test"), *TTL, |txt, _| Ok(txt)).unwrap(); - assert_eq!(val, "This page works!",); + let val = fetch(url, *TTL, |txt, _| Ok(txt)).unwrap(); + assert_eq!(val, "It works",); // The cache should now be hit - let val = try_load_cache(&server.url("/test"), Duration::max_value()).unwrap(); + let val = try_load_cache(url, Duration::max_value()).unwrap(); print_cache_list("After second read"); assert_eq!( val, CacheResult::Hit(( - "This page works!".into(), + "It works".into(), Headers { etag: Some("static".into()), - this_page: None, + this_page: Some(1), next_page: None, - last_page: None, + last_page: Some(1), } )) ); // Let's fake a stale entry thread::sleep(std::time::Duration::from_secs(1)); - let val = try_load_cache(&server.url("/test"), Duration::zero()).unwrap(); + let val = try_load_cache(url, Duration::zero()).unwrap(); assert!(matches!(val, CacheResult::Stale(_, _))); } diff --git a/src/cache/wrapper.rs b/src/cache/wrapper.rs index c61748f..e6e019d 100644 --- a/src/cache/wrapper.rs +++ b/src/cache/wrapper.rs @@ -3,7 +3,6 @@ //! To make testing easier. use cacache::Metadata; use lazy_static::lazy_static; -use reqwest::blocking::{RequestBuilder, Response}; use tracing::info; use std::{io::Write, path::Path}; @@ -42,10 +41,6 @@ pub fn clear_cache() -> Result<()> { cacache::clear_sync(cache()).map_err(|why| Error::Cache(why, "clearing")) } -pub fn send_request(builder: RequestBuilder) -> Result { - builder.send().map_err(Error::Reqwest) -} - #[cfg(test)] pub fn list_cache() -> impl Iterator> { cacache::list_sync(cache()) diff --git a/src/config/mod.rs b/src/config/mod.rs index 5dbdb51..12592ba 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,10 +1,9 @@ use chrono::NaiveDate; use lazy_static::lazy_static; -use reqwest::blocking::Client; use serde::Deserialize; use structopt::{clap::arg_enum, StructOpt}; -use std::{collections::HashSet, fs, path::Path, time::Duration as StdDuration}; +use std::{collections::HashSet, fs, path::Path}; use crate::{ canteen::CanteenId, @@ -22,32 +21,22 @@ pub mod args; pub mod rule; lazy_static! { - pub static ref CONF: Config = Config::assemble().unwrap(); - static ref REQUEST_TIMEOUT: StdDuration = StdDuration::from_secs(10); + pub static ref CONF: Config = Config::assemble(); } #[derive(Debug)] pub struct Config { pub config: Option, - pub client: Client, pub args: Args, } impl Config { - fn assemble() -> Result { + fn assemble() -> Self { let args = Args::from_args(); let default_config_path = || DIR.config_dir().join("config.toml"); let path = args.config.clone().unwrap_or_else(default_config_path); let config = ConfigFile::load_or_log(path); - let client = Client::builder() - .timeout(*REQUEST_TIMEOUT) - .build() - .map_err(Error::Reqwest)?; - Ok(Config { - config, - client, - args, - }) + Config { config, args } } /// Easy reference to the Command diff --git a/src/main.rs b/src/main.rs index e262434..75cd919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,6 +133,7 @@ mod error; mod geoip; mod meal; mod pagination; +mod request; mod tag; // #[cfg(test)] // mod tests; diff --git a/src/request/dummy.rs b/src/request/dummy.rs new file mode 100644 index 0000000..63d376c --- /dev/null +++ b/src/request/dummy.rs @@ -0,0 +1,47 @@ +//! This contains the [`DummyApi`] used for testing purposes. +use reqwest::StatusCode; + +use crate::error::Result; + +use super::{Api, Headers, Response}; + +/// A dummy API, serving local, deterministic Responses +#[derive(Debug)] +pub struct DummyApi; + +impl Api for DummyApi { + fn create() -> Result { + Ok(DummyApi) + } + + fn get<'url, S>(&self, url: &'url str, etag: Option) -> Result> + where + S: AsRef, + { + if url == "http://invalid.local/test" { + get_test_page(etag) + } else { + panic!("BUG: Invalid url in dummy api: {:?}", url) + } + } +} + +/// GET http://invalid.local/test +fn get_test_page>(etag: Option) -> Result> { + let etag = etag.map(|etag| etag.as_ref().to_owned()); + Ok(Response { + url: "http://invalid.local/test", + status: if etag == Some("static".into()) { + StatusCode::NOT_MODIFIED + } else { + StatusCode::OK + }, + headers: Headers { + etag: Some("static".into()), + this_page: Some(1), + next_page: None, + last_page: Some(1), + }, + body: "It works".to_owned(), + }) +} diff --git a/src/request/mod.rs b/src/request/mod.rs new file mode 100644 index 0000000..741d885 --- /dev/null +++ b/src/request/mod.rs @@ -0,0 +1,51 @@ +use ::reqwest::StatusCode; +use serde::{Deserialize, Serialize}; + +use crate::error::Result; + +#[cfg(not(test))] +mod reqwest; +#[cfg(not(test))] +pub use self::reqwest::ReqwestApi as DefaultApi; + +#[cfg(test)] +mod dummy; +#[cfg(test)] +pub use self::dummy::DummyApi as DefaultApi; + +/// Assortment of headers relevant to the program. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] +pub struct Headers { + pub etag: Option, + pub this_page: Option, + pub next_page: Option, + pub last_page: Option, +} + +/// A subset of a Response, derived from [`reqwest::Response`]. +pub struct Response<'url> { + pub url: &'url str, + pub status: StatusCode, + pub headers: Headers, + pub body: String, +} + +/// Generalized API endpoint. +/// +/// This abstracts away from the real thing to allow for deterministic local +/// tests with a DummyApi. +pub trait Api +where + Self: Sized, +{ + /// Create the Api. + fn create() -> Result; + + /// Send a get request. + /// + /// Optionally attach an `If-None-Match` header, if `etag` is `Some`. + fn get<'url, S>(&self, url: &'url str, etag: Option) -> Result> + where + S: AsRef; +} diff --git a/src/request/reqwest.rs b/src/request/reqwest.rs new file mode 100644 index 0000000..aafc17d --- /dev/null +++ b/src/request/reqwest.rs @@ -0,0 +1,92 @@ +use lazy_static::lazy_static; +use regex::Regex; +use reqwest::blocking::Client; + +use std::time::Duration as StdDuration; + +use crate::error::{Error, Result}; + +use super::{Api, Headers, Response}; + +lazy_static! { + /// Regex to find the next page in a link header + /// Probably only applicable to the current version of the openmensa API. + // TODO: Improve this. How do these LINK headers look in general? + static ref LINK_NEXT_PAGE_RE: Regex = Regex::new(r#"<([^>]*)>; rel="next""#).unwrap(); + static ref REQUEST_TIMEOUT: StdDuration = StdDuration::from_secs(10); +} + +/// Real api accessing the inter-webs. +#[derive(Debug)] +pub struct ReqwestApi { + client: Client, +} + +impl Api for ReqwestApi { + fn create() -> Result { + let client = Client::builder() + .timeout(*REQUEST_TIMEOUT) + .build() + .map_err(Error::Reqwest)?; + Ok(ReqwestApi { client }) + } + + fn get<'url, S>(&self, url: &'url str, etag: Option) -> Result> + where + S: AsRef, + { + let mut builder = self.client.get(url); + if let Some(etag) = etag { + let etag_key = reqwest::header::IF_NONE_MATCH; + builder = builder.header(etag_key, etag.as_ref()); + } + let resp = builder.send().map_err(Error::Reqwest)?; + Ok(Response { + url, + status: resp.status(), + headers: resp.headers().clone().into(), + body: resp.text().map_err(Error::Reqwest)?, + }) + } +} + +impl From for Headers { + fn from(map: reqwest::header::HeaderMap) -> Self { + use reqwest::header::*; + let etag = map + .get(ETAG) + .map(|raw| { + let utf8 = raw.to_str().ok()?; + Some(utf8.to_string()) + }) + .flatten(); + let this_page = map + .get("x-current-page") + .map(|raw| { + let utf8 = raw.to_str().ok()?; + utf8.parse().ok() + }) + .flatten(); + let next_page = map + .get(LINK) + .map(|raw| { + let utf8 = raw.to_str().ok()?; + let captures = LINK_NEXT_PAGE_RE.captures(utf8)?; + Some(captures[1].to_owned()) + }) + .flatten(); + let last_page = map + .get("x-total-pages") + .map(|raw| { + let utf8 = raw.to_str().ok()?; + utf8.parse().ok() + }) + .flatten(); + Self { + etag, + this_page, + last_page, + next_page, + } + } +}