diff --git a/.github/workflows/http-cache.yml b/.github/workflows/http-cache.yml index 9692952..2593271 100644 --- a/.github/workflows/http-cache.yml +++ b/.github/workflows/http-cache.yml @@ -42,10 +42,11 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: | + cargo test --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat + cargo test --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat + cargo test --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat + cargo test --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat cargo test --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol - cargo test --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio - cargo test --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting - cargo test --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting clippy: name: Check clippy @@ -57,10 +58,11 @@ jobs: with: components: "clippy" - run: | + cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat -- -D warnings + cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat -- -D warnings + cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat -- -D warnings + cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat -- -D warnings cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol -- -D warnings - cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio -- -D warnings - cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting -- -D warnings - cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting -- -D warnings docs: name: Build docs @@ -74,7 +76,7 @@ jobs: RUSTDOCFLAGS: --cfg docsrs -Dwarnings - run: | cargo doc --no-deps --document-private-items - cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol - cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio - cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting - cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting + cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat + cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat + cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat + cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat diff --git a/http-cache-reqwest/src/lib.rs b/http-cache-reqwest/src/lib.rs index 82408a9..7f2e20c 100644 --- a/http-cache-reqwest/src/lib.rs +++ b/http-cache-reqwest/src/lib.rs @@ -279,9 +279,7 @@ pub type ReqwestStreamingError = http_cache::ClientStreamingError; #[cfg(feature = "streaming")] use http_cache::StreamingCacheManager; -use std::{ - collections::HashMap, convert::TryInto, str::FromStr, time::SystemTime, -}; +use std::{convert::TryInto, str::FromStr, time::SystemTime}; pub use http::request::Parts; use http::{ @@ -443,13 +441,7 @@ impl Middleware for ReqwestMiddleware<'_> { .run(copied_req, self.extensions) .await .map_err(BoxError::from)?; - let mut headers = HashMap::new(); - for header in res.headers() { - headers.insert( - header.0.as_str().to_owned(), - header.1.to_str()?.to_owned(), - ); - } + let headers = res.headers().into(); let url = res.url().clone(); let status = res.status().into(); let version = res.version(); diff --git a/http-cache-reqwest/src/test.rs b/http-cache-reqwest/src/test.rs index 1e9220d..2cd2ebb 100644 --- a/http-cache-reqwest/src/test.rs +++ b/http-cache-reqwest/src/test.rs @@ -1750,6 +1750,89 @@ mod streaming_tests { Ok(()) } + #[tokio::test] + async fn streaming_cache_with_multiple_vary_headers() -> Result<()> { + let manager = create_streaming_cache_manager(); + let cache = HttpStreamingCache { + mode: CacheMode::Default, + manager, + options: Default::default(), + }; + + // Create a request + let request = Request::builder() + .uri("https://example.com/multiple-vary-test") + .header("user-agent", "test-agent") + .header("accept-encoding", "gzip") + .header("accept-language", "en-US") + .body(()) + .unwrap(); + + let (parts, _) = request.into_parts(); + let analysis = cache.analyze_request(&parts, None)?; + + // Create a response with MULTIPLE Vary headers (the issue from #119) + let mut response = Response::builder() + .status(200) + .header("content-type", "application/json") + .header("cache-control", "max-age=3600") + .body(Full::new(Bytes::from("multiple vary test data"))) + .unwrap(); + + // Add multiple Vary headers using headers_mut() + let headers = response.headers_mut(); + headers.append("vary", "Prefer".parse().unwrap()); + headers.append("vary", "Accept".parse().unwrap()); + headers.append("vary", "Range".parse().unwrap()); + headers.append("vary", "Accept-Encoding".parse().unwrap()); + headers.append("vary", "Accept-Language".parse().unwrap()); + headers.append("vary", "Accept-Datetime".parse().unwrap()); + + // Process the response + let cached_response = + cache.process_response(analysis.clone(), response).await?; + assert_eq!(cached_response.status(), 200); + + // Verify the body + let body_bytes = + cached_response.into_body().collect().await?.to_bytes(); + assert_eq!(body_bytes, "multiple vary test data"); + + // Test cache lookup - this is where the bug would show up + let cached_result = + cache.lookup_cached_response(&analysis.cache_key).await?; + assert!(cached_result.is_some()); + + if let Some((response, _policy)) = cached_result { + // Get ALL Vary header values + let vary_values: Vec<_> = response + .headers() + .get_all("vary") + .iter() + .map(|v| v.to_str().unwrap()) + .collect(); + + // Verify we got all 6 Vary headers, not just one + assert_eq!( + vary_values.len(), + 6, + "Expected 6 Vary headers, got {}: {:?}", + vary_values.len(), + vary_values + ); + + // Verify all expected values are present + assert!(vary_values.contains(&"Prefer")); + assert!(vary_values.contains(&"Accept")); + assert!(vary_values.contains(&"Range")); + assert!(vary_values.contains(&"Accept-Encoding")); + assert!(vary_values.contains(&"Accept-Language")); + assert!(vary_values.contains(&"Accept-Datetime")); + } + + Ok(()) + } + #[cfg(feature = "rate-limiting")] #[tokio::test] async fn test_streaming_with_rate_limiting() -> Result<()> { diff --git a/http-cache-surf/src/lib.rs b/http-cache-surf/src/lib.rs index 77c73b9..c11389a 100644 --- a/http-cache-surf/src/lib.rs +++ b/http-cache-surf/src/lib.rs @@ -144,8 +144,8 @@ //! ``` use std::convert::TryInto; +use std::str::FromStr; use std::time::SystemTime; -use std::{collections::HashMap, str::FromStr}; use http::{ header::CACHE_CONTROL, @@ -155,7 +155,7 @@ use http_cache::{ BadHeader, BoxError, CacheManager, CacheOptions, HitOrMiss, HttpResponse, Middleware, Result, XCACHE, XCACHELOOKUP, }; -pub use http_cache::{CacheMode, HttpCache}; +pub use http_cache::{CacheMode, HttpCache, HttpHeaders}; use http_cache_semantics::CachePolicy; use http_types::{ headers::HeaderValue as HttpTypesHeaderValue, @@ -258,7 +258,7 @@ impl Middleware for SurfMiddleware<'_> { let url = self.req.url().clone(); let mut res = self.next.run(self.req.clone().into(), self.client.clone()).await?; - let mut headers = HashMap::new(); + let mut headers = HttpHeaders::new(); for header in res.iter() { headers.insert( header.0.as_str().to_owned(), diff --git a/http-cache-ureq/src/lib.rs b/http-cache-ureq/src/lib.rs index d59f5da..2067941 100644 --- a/http-cache-ureq/src/lib.rs +++ b/http-cache-ureq/src/lib.rs @@ -584,11 +584,14 @@ impl<'a, T: CacheManager> CachedRequestBuilder<'a, T> { if cache_status_headers { cached_response .headers - .insert(XCACHE.to_string(), HitOrMiss::MISS.to_string()); - cached_response.headers.insert( - XCACHELOOKUP.to_string(), - HitOrMiss::MISS.to_string(), - ); + .entry(XCACHE.to_string()) + .or_insert_with(Vec::new) + .push(HitOrMiss::MISS.to_string()); + cached_response + .headers + .entry(XCACHELOOKUP.to_string()) + .or_insert_with(Vec::new) + .push(HitOrMiss::MISS.to_string()); } Ok(cached_response) @@ -664,18 +667,9 @@ fn convert_ureq_response_to_http_response( url: &str, ) -> Result { let status = response.status(); - let mut headers = HashMap::new(); // Copy headers - for (name, value) in response.headers() { - let value_str = value.to_str().map_err(|e| { - HttpCacheError::http(Box::new(std::io::Error::other(format!( - "Invalid header value: {}", - e - )))) - })?; - headers.insert(name.as_str().to_string(), value_str.to_string()); - } + let headers = response.headers().into(); // Read body using read_to_string let body_string = response.body_mut().read_to_string().map_err(|e| { @@ -708,7 +702,7 @@ fn convert_ureq_response_to_http_response( #[derive(Debug)] pub struct CachedResponse { status: u16, - headers: HashMap, + headers: HashMap>, body: Vec, url: String, cached: bool, @@ -727,7 +721,9 @@ impl CachedResponse { /// Get a header value pub fn header(&self, name: &str) -> Option<&str> { - self.headers.get(name).map(|s| s.as_str()) + self.headers + .get(name) + .and_then(|values| values.first().map(|s| s.as_str())) } /// Get all header names @@ -786,7 +782,10 @@ impl CachedResponse { let mut headers = HashMap::new(); for (name, value) in response.headers() { let value_str = value.to_str().unwrap_or(""); - headers.insert(name.as_str().to_string(), value_str.to_string()); + headers + .entry(name.as_str().to_string()) + .or_insert_with(Vec::new) + .push(value_str.to_string()); } // Note: Cache headers will be added by the cache system based on cache_status_headers option @@ -810,7 +809,7 @@ impl From for CachedResponse { // based on the cache_status_headers option, so don't add them here Self { status: response.status, - headers: response.headers, + headers: response.headers.into(), body: response.body, url: response.url.to_string(), cached: true, diff --git a/http-cache/Cargo.toml b/http-cache/Cargo.toml index ef48352..c1c1150 100644 --- a/http-cache/Cargo.toml +++ b/http-cache/Cargo.toml @@ -55,6 +55,7 @@ smol-macros = "0.1.1" [features] default = ["manager-cacache", "cacache-smol"] +http-headers-compat = [] manager-cacache = ["cacache", "bincode"] cacache-tokio = ["cacache/tokio-runtime", "tokio", "bincode"] cacache-smol = ["cacache/async-std", "smol"] diff --git a/http-cache/src/lib.rs b/http-cache/src/lib.rs index f1b15e1..dd827c5 100644 --- a/http-cache/src/lib.rs +++ b/http-cache/src/lib.rs @@ -233,6 +233,9 @@ //! - `manager-cacache` (default): enable [cacache](https://github.com/zkat/cacache-rs), //! a disk cache, backend manager. //! - `cacache-smol` (default): enable [smol](https://github.com/smol-rs/smol) runtime support for cacache. +//! - `http-headers-compat` (disabled): enable backwards compatibility for deserializing cached +//! responses from older versions that used single-value headers. Enable this if you need to read +//! cache entries created by older versions of http-cache. //! - `cacache-tokio` (disabled): enable [tokio](https://github.com/tokio-rs/tokio) runtime support for cacache. //! - `manager-moka` (disabled): enable [moka](https://github.com/moka-rs/moka), //! an in-memory cache, backend manager. @@ -276,7 +279,7 @@ use http::{ header::CACHE_CONTROL, request, response, HeaderValue, Response, StatusCode, }; use http_cache_semantics::{AfterResponse, BeforeRequest, CachePolicy}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use url::Url; pub use body::StreamingBody; @@ -424,13 +427,267 @@ fn determine_scheme(host: &str, headers: &http::HeaderMap) -> Result { } } +/// Represents HTTP headers in either legacy or modern format +#[derive(Debug, Clone)] +pub enum HttpHeaders { + /// Modern header representation - allows multiple values per key + Modern(HashMap>), + /// Legacy header representation - kept for backward compatibility with deserialization + #[cfg(feature = "http-headers-compat")] + Legacy(HashMap), +} + +// Serialize as enum (needed for bincode to match deserialization) +impl Serialize for HttpHeaders { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: Serializer, + { + // Serialize the enum properly with variant tags + // This matches how RawHttpHeaders deserializes + match self { + HttpHeaders::Modern(modern) => serializer + .serialize_newtype_variant("HttpHeaders", 0, "Modern", modern), + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => serializer + .serialize_newtype_variant("HttpHeaders", 1, "Legacy", legacy), + } + } +} + +// Deserialize: Need to handle enum variant properly for bincode +impl<'de> Deserialize<'de> for HttpHeaders { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + // Bincode serializes enums with a variant index + // We need to deserialize it properly as an enum + #[cfg(feature = "http-headers-compat")] + { + #[derive(Deserialize)] + enum RawHttpHeaders { + Modern(HashMap>), + Legacy(HashMap), + } + + match RawHttpHeaders::deserialize(deserializer)? { + RawHttpHeaders::Modern(m) => Ok(HttpHeaders::Modern(m)), + RawHttpHeaders::Legacy(l) => Ok(HttpHeaders::Legacy(l)), + } + } + + #[cfg(not(feature = "http-headers-compat"))] + { + #[derive(Deserialize)] + enum RawHttpHeaders { + Modern(HashMap>), + } + + match RawHttpHeaders::deserialize(deserializer)? { + RawHttpHeaders::Modern(m) => Ok(HttpHeaders::Modern(m)), + } + } + } +} + +impl HttpHeaders { + /// Creates a new empty HttpHeaders in modern format + pub fn new() -> Self { + HttpHeaders::Modern(HashMap::new()) + } + + /// Inserts a header key-value pair, replacing any existing values for that key + pub fn insert(&mut self, key: String, value: String) { + match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => { + legacy.insert(key, value); + } + HttpHeaders::Modern(modern) => { + // Replace existing values with a new single-element vec + modern.insert(key, vec![value]); + } + } + } + + /// Retrieves the first value for a given header key + pub fn get(&self, key: &str) -> Option<&String> { + match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => legacy.get(key), + HttpHeaders::Modern(modern) => { + modern.get(key).and_then(|vals| vals.first()) + } + } + } + + /// Removes a header key and its associated values + pub fn remove(&mut self, key: &str) { + match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => { + legacy.remove(key); + } + HttpHeaders::Modern(modern) => { + modern.remove(key); + } + } + } + + /// Checks if a header key exists + pub fn contains_key(&self, key: &str) -> bool { + match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => legacy.contains_key(key), + HttpHeaders::Modern(modern) => modern.contains_key(key), + } + } + + /// Returns an iterator over the header key-value pairs + pub fn iter(&self) -> HttpHeadersIterator<'_> { + match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => { + HttpHeadersIterator { inner: legacy.iter().collect(), index: 0 } + } + HttpHeaders::Modern(modern) => HttpHeadersIterator { + inner: modern + .iter() + .flat_map(|(k, vals)| vals.iter().map(move |v| (k, v))) + .collect(), + index: 0, + }, + } + } +} + +impl From<&http::HeaderMap> for HttpHeaders { + fn from(headers: &http::HeaderMap) -> Self { + let mut modern_headers = HashMap::new(); + + // Collect all unique header names first + let header_names: std::collections::HashSet<_> = + headers.keys().collect(); + + // For each header name, collect ALL values + for name in header_names { + let values: Vec = headers + .get_all(name) + .iter() + .filter_map(|v| v.to_str().ok()) + .map(|s| s.to_string()) + .collect(); + + if !values.is_empty() { + modern_headers.insert(name.to_string(), values); + } + } + + HttpHeaders::Modern(modern_headers) + } +} + +impl From for HashMap> { + fn from(headers: HttpHeaders) -> Self { + match headers { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => { + legacy.into_iter().map(|(k, v)| (k, vec![v])).collect() + } + HttpHeaders::Modern(modern) => modern, + } + } +} + +impl Default for HttpHeaders { + fn default() -> Self { + HttpHeaders::new() + } +} + +impl IntoIterator for HttpHeaders { + type Item = (String, String); + type IntoIter = HttpHeadersIntoIterator; + + fn into_iter(self) -> Self::IntoIter { + HttpHeadersIntoIterator { + inner: match self { + #[cfg(feature = "http-headers-compat")] + HttpHeaders::Legacy(legacy) => legacy.into_iter().collect(), + HttpHeaders::Modern(modern) => modern + .into_iter() + .flat_map(|(k, vals)| { + vals.into_iter().map(move |v| (k.clone(), v)) + }) + .collect(), + }, + index: 0, + } + } +} + +/// Iterator for HttpHeaders +#[derive(Debug)] +pub struct HttpHeadersIntoIterator { + inner: Vec<(String, String)>, + index: usize, +} + +impl Iterator for HttpHeadersIntoIterator { + type Item = (String, String); + + fn next(&mut self) -> Option { + if self.index < self.inner.len() { + let item = self.inner[self.index].clone(); + self.index += 1; + Some(item) + } else { + None + } + } +} + +impl<'a> IntoIterator for &'a HttpHeaders { + type Item = (&'a String, &'a String); + type IntoIter = HttpHeadersIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Iterator for HttpHeaders references +#[derive(Debug)] +pub struct HttpHeadersIterator<'a> { + inner: Vec<(&'a String, &'a String)>, + index: usize, +} + +impl<'a> Iterator for HttpHeadersIterator<'a> { + type Item = (&'a String, &'a String); + + fn next(&mut self) -> Option { + if self.index < self.inner.len() { + let item = self.inner[self.index]; + self.index += 1; + Some(item) + } else { + None + } + } +} + /// A basic generic type that represents an HTTP response #[derive(Debug, Clone, Deserialize, Serialize)] pub struct HttpResponse { /// HTTP response body pub body: Vec, /// HTTP response headers - pub headers: HashMap, + pub headers: HttpHeaders, /// HTTP response status code pub status: u16, /// HTTP response url @@ -1175,16 +1432,6 @@ impl HttpCacheOptions { self.create_cache_key(parts, Some(method_override)) } - /// Converts http::HeaderMap to HashMap for HttpResponse - fn headers_to_hashmap( - headers: &http::HeaderMap, - ) -> HashMap { - headers - .iter() - .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) - .collect() - } - /// Converts HttpResponse to http::Response with the given body type pub fn http_response_to_response( http_response: &HttpResponse, @@ -1214,7 +1461,7 @@ impl HttpCacheOptions { ) -> Result { Ok(HttpResponse { body: vec![], // We don't need the full body for cache mode decision - headers: Self::headers_to_hashmap(&parts.headers), + headers: (&parts.headers).into(), status: parts.status.as_u16(), url: extract_url_from_request_parts(request_parts)?, version: parts.version.try_into()?, @@ -1536,7 +1783,7 @@ impl HttpCache { // ENOTCACHED let mut res = HttpResponse { body: b"GatewayTimeout".to_vec(), - headers: HashMap::default(), + headers: HttpHeaders::default(), status: 504, url: middleware.url()?, version: HttpVersion::Http11, diff --git a/http-cache/src/managers/streaming_cache.rs b/http-cache/src/managers/streaming_cache.rs index 252c9b3..3742c4d 100644 --- a/http-cache/src/managers/streaming_cache.rs +++ b/http-cache/src/managers/streaming_cache.rs @@ -27,8 +27,6 @@ use { std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}, }; -use std::collections::HashMap; - // Import async-compatible synchronization primitives based on feature flags cfg_if::cfg_if! { if #[cfg(feature = "streaming-tokio")] { @@ -348,7 +346,7 @@ pub struct CacheIntegrityReport { pub struct CacheMetadata { pub status: u16, pub version: u8, - pub headers: HashMap, + pub headers: crate::HttpHeaders, pub content_digest: String, pub policy: CachePolicy, pub created_at: u64, @@ -1086,9 +1084,7 @@ impl StreamingCacheManager for StreamingManager { Version::HTTP_3 => 3, _ => 11, }, - headers: crate::HttpCacheOptions::headers_to_hashmap( - &parts.headers, - ), + headers: crate::HttpHeaders::from(&parts.headers), content_digest: content_digest.clone(), policy, created_at: std::time::SystemTime::now() diff --git a/http-cache/src/test.rs b/http-cache/src/test.rs index 72a3d94..939194d 100644 --- a/http-cache/src/test.rs +++ b/http-cache/src/test.rs @@ -1,8 +1,10 @@ -use crate::{error, CacheMode, HitOrMiss, HttpResponse, HttpVersion, Result}; +use crate::{ + error, CacheMode, HitOrMiss, HttpHeaders, HttpResponse, HttpVersion, Result, +}; use http::{header::CACHE_CONTROL, StatusCode}; use url::Url; -use std::{collections::HashMap, str::FromStr}; +use std::str::FromStr; #[cfg(feature = "cacache-smol")] use macro_rules_attribute::apply; @@ -51,12 +53,12 @@ fn response_methods_work() -> Result<()> { let url = Url::from_str("http://example.com")?; let mut res = HttpResponse { body: TEST_BODY.to_vec(), - headers: HashMap::default(), + headers: HttpHeaders::new(), status: 200, url: url.clone(), version: HttpVersion::Http11, }; - assert_eq!(format!("{:?}", res.clone()), "HttpResponse { body: [116, 101, 115, 116], headers: {}, status: 200, url: Url { scheme: \"http\", cannot_be_a_base: false, username: \"\", password: None, host: Some(Domain(\"example.com\")), port: None, path: \"/\", query: None, fragment: None }, version: Http11 }"); + assert_eq!(format!("{:?}", res.clone()), "HttpResponse { body: [116, 101, 115, 116], headers: Modern({}), status: 200, url: Url { scheme: \"http\", cannot_be_a_base: false, username: \"\", password: None, host: Some(Domain(\"example.com\")), port: None, path: \"/\", query: None, fragment: None }, version: Http11 }"); res.add_warning(&url, 112, "Test Warning"); let code = res.warning_code(); assert!(code.is_some()); @@ -595,7 +597,7 @@ mod interface_tests { // Create a cached response let cached_response = crate::HttpResponse { body: b"Cached content".to_vec(), - headers: std::collections::HashMap::new(), + headers: crate::HttpHeaders::new(), status: 200, url: Url::parse("https://example.com/test").unwrap(), version: crate::HttpVersion::Http11, @@ -636,7 +638,7 @@ mod interface_tests { // Create a cached response let cached_response = crate::HttpResponse { body: b"Cached content".to_vec(), - headers: std::collections::HashMap::new(), + headers: crate::HttpHeaders::new(), status: 200, url: Url::parse("https://example.com/test").unwrap(), version: crate::HttpVersion::Http11, @@ -879,7 +881,7 @@ mod interface_tests { // Create a cached response let cached_response = crate::HttpResponse { body: b"Cached content".to_vec(), - headers: std::collections::HashMap::new(), + headers: crate::HttpHeaders::new(), status: 200, url: Url::parse("https://example.com/test").unwrap(), version: crate::HttpVersion::Http11, @@ -920,7 +922,7 @@ mod interface_tests { // Create a cached response let cached_response = crate::HttpResponse { body: b"Cached content".to_vec(), - headers: std::collections::HashMap::new(), + headers: crate::HttpHeaders::new(), status: 200, url: Url::parse("https://example.com/test").unwrap(), version: crate::HttpVersion::Http11, diff --git a/justfile b/justfile index ae8331d..8e83e0b 100644 --- a/justfile +++ b/justfile @@ -5,13 +5,15 @@ # Run tests on all crates with proper feature combinations using nextest @test: echo "----------\nCore library (smol):\n" - cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol + cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat echo "\n----------\nCore library (tokio):\n" - cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio + cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat echo "\n----------\nCore library (smol + rate-limiting):\n" - cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting + cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat echo "\n----------\nCore library (tokio + rate-limiting):\n" - cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting + cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat + echo "\n----------\nCore library (smol, no http-headers-compat):\n" + cd http-cache && cargo nextest run --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol echo "\n----------\nReqwest middleware:\n" cd http-cache-reqwest && cargo nextest run --all-features echo "\n----------\nSurf middleware:\n" @@ -27,13 +29,13 @@ # Run doctests on all crates with proper feature combinations @doctest: echo "----------\nCore library (smol):\n" - cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol + cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat echo "\n----------\nCore library (tokio):\n" - cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio + cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat echo "\n----------\nCore library (smol + rate-limiting):\n" - cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting + cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat echo "\n----------\nCore library (tokio + rate-limiting):\n" - cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting + cd http-cache && cargo test --doc --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat echo "\n----------\nReqwest middleware:\n" cd http-cache-reqwest && cargo test --doc --all-features echo "\n----------\nSurf middleware:\n" @@ -48,13 +50,15 @@ @check: echo "----------\nCore library (smol):\n" - cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol + cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat echo "\n----------\nCore library (tokio):\n" - cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio + cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat echo "\n----------\nCore library (smol + rate-limiting):\n" - cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting + cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat echo "\n----------\nCore library (tokio + rate-limiting):\n" - cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting + cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat + echo "\n----------\nCore library (smol, no http-headers-compat):\n" + cd http-cache && cargo check --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol echo "\n----------\nReqwest middleware:\n" cd http-cache-reqwest && cargo check --all-features echo "\n----------\nSurf middleware:\n" @@ -120,13 +124,15 @@ changelog TAG: # Lint all crates with clippy and check formatting @lint: echo "----------\nCore library (smol):\n" - cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol -- -D warnings + cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,http-headers-compat -- -D warnings echo "\n----------\nCore library (tokio):\n" - cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio -- -D warnings + cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,http-headers-compat -- -D warnings echo "\n----------\nCore library (smol + rate-limiting):\n" - cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting -- -D warnings + cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol,rate-limiting,http-headers-compat -- -D warnings echo "\n----------\nCore library (tokio + rate-limiting):\n" - cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting -- -D warnings + cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-tokio,with-http-types,manager-moka,streaming-tokio,rate-limiting,http-headers-compat -- -D warnings + echo "\n----------\nCore library (smol, no http-headers-compat):\n" + cd http-cache && cargo clippy --lib --tests --all-targets --no-default-features --features manager-cacache,cacache-smol,with-http-types,manager-moka,streaming-smol -- -D warnings echo "\n----------\nReqwest middleware:\n" cd http-cache-reqwest && cargo clippy --lib --tests --all-targets --all-features -- -D warnings echo "\n----------\nSurf middleware:\n"