use http_client::Request;
use http_client_vcr::{RequestMatcher, SerializableRequest};
use std::fmt::Debug;
#[derive(Debug)]
pub struct LastFmEditVcrMatcher {
match_method: bool,
match_url: bool,
match_body: bool,
}
impl LastFmEditVcrMatcher {
pub fn new() -> Self {
Self {
match_method: true,
match_url: true,
match_body: false,
}
}
pub fn with_body(mut self, match_body: bool) -> Self {
self.match_body = match_body;
self
}
}
impl RequestMatcher for LastFmEditVcrMatcher {
fn matches(&self, request: &Request, recorded_request: &SerializableRequest) -> bool {
log::debug!(
"Matching request: {} {} against recorded: {} {}",
request.method(),
request.url(),
recorded_request.method,
recorded_request.url
);
if self.match_method && request.method().to_string() != recorded_request.method {
log::debug!(
"Method mismatch: {} != {}",
request.method(),
recorded_request.method
);
return false;
}
if self.match_url && request.url().to_string() != recorded_request.url {
log::debug!(
"URL mismatch: {} != {}",
request.url(),
recorded_request.url
);
return false;
}
let unstable_headers = [
"cookie",
"set-cookie",
"authorization",
"x-csrf-token",
"csrf-token",
"sessionid",
"session",
"x-session-id",
"x-auth-token",
"auth-token",
"accept",
"upgrade-insecure-requests",
];
log::debug!("Checking headers (ignoring unstable ones)");
for (header_name, recorded_values) in &recorded_request.headers {
let header_lower = header_name.to_lowercase();
if unstable_headers
.iter()
.any(|unstable| header_lower.contains(unstable))
{
log::debug!("Skipping unstable header: {header_name}");
continue;
}
let request_header = request.header(header_name.as_str());
log::debug!(
"Comparing stable header '{header_name}': request={:?}, recorded={recorded_values:?}",
request_header.map(|v| v.iter().map(|h| h.as_str()).collect::<Vec<_>>())
);
match request_header {
Some(req_val) => {
let req_values: Vec<String> =
req_val.iter().map(|v| v.as_str().to_string()).collect();
if &req_values != recorded_values {
log::debug!(
"Header '{header_name}' values mismatch: {req_values:?} != {recorded_values:?}"
);
return false;
}
}
None => {
log::debug!("Header '{header_name}' missing from request");
return false;
}
}
}
log::debug!("Request matched successfully");
true
}
fn matches_serializable(
&self,
request: &SerializableRequest,
recorded_request: &SerializableRequest,
) -> bool {
log::debug!(
"Matching serializable request: {} {} vs {} {}",
request.method,
request.url,
recorded_request.method,
recorded_request.url
);
if self.match_method && request.method != recorded_request.method {
return false;
}
if self.match_url && request.url != recorded_request.url {
return false;
}
let unstable_headers = [
"cookie",
"set-cookie",
"authorization",
"x-csrf-token",
"csrf-token",
"sessionid",
"session",
"x-session-id",
"x-auth-token",
"auth-token",
"accept",
"upgrade-insecure-requests",
];
log::debug!(
"Checking {} recorded headers",
recorded_request.headers.len()
);
for (header_name, recorded_values) in &recorded_request.headers {
let header_lower = header_name.to_lowercase();
if unstable_headers
.iter()
.any(|unstable| header_lower.contains(unstable))
{
log::debug!("Skipping unstable header: {header_name}");
continue;
}
log::debug!("Checking stable header: {header_name} = {recorded_values:?}");
let request_header = request.headers.get(header_name);
match request_header {
Some(req_values) => {
log::debug!(
"Comparing header '{header_name}': request={req_values:?} vs recorded={recorded_values:?}"
);
if req_values != recorded_values {
log::debug!(
"Header '{header_name}' MISMATCH! request={req_values:?} != {recorded_values:?}"
);
return false;
}
}
None => {
if header_name.to_lowercase() == "content-type" && request.method == "GET" {
log::debug!("Ignoring missing content-type header for GET request");
continue;
}
log::debug!(
"Header '{header_name}' missing from current request (recorded has: {recorded_values:?})"
);
return false;
}
}
}
true
}
}
impl Default for LastFmEditVcrMatcher {
fn default() -> Self {
Self::new()
}
}