use crate::backend::ResponseRecord;
use crate::config::{IgnoreField, ShadowConfig};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Divergence {
pub status: Option<(u16, u16)>,
pub headers: Option<HeaderDiff>,
pub body: Option<BodyDiff>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeaderDiff {
pub added: Vec<String>,
pub removed: Vec<String>,
pub changed: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BodyDiff {
pub primary_len: usize,
pub shadow_len: usize,
pub prefix_equal_bytes: usize,
}
impl Divergence {
pub fn compare(
primary: &ResponseRecord,
shadow: &ResponseRecord,
config: &ShadowConfig,
) -> Option<Self> {
let status =
if config.ignore.contains(&IgnoreField::Status) || primary.status == shadow.status {
None
} else {
Some((primary.status, shadow.status))
};
let headers = if config.ignore.contains(&IgnoreField::Headers) {
None
} else {
Self::diff_headers(primary, shadow)
};
let body = if config.ignore.contains(&IgnoreField::Body) || primary.body == shadow.body {
None
} else {
Some(BodyDiff {
primary_len: primary.body.len(),
shadow_len: shadow.body.len(),
prefix_equal_bytes: shared_prefix(&primary.body, &shadow.body),
})
};
if status.is_none() && headers.is_none() && body.is_none() {
None
} else {
Some(Self {
status,
headers,
body,
})
}
}
fn diff_headers(primary: &ResponseRecord, shadow: &ResponseRecord) -> Option<HeaderDiff> {
let mut added = Vec::new();
let mut removed = Vec::new();
let mut changed = Vec::new();
for (k, v) in &primary.headers {
match shadow.headers.get(k) {
None => removed.push(k.clone()),
Some(sv) if sv != v => changed.push(k.clone()),
_ => {}
}
}
for k in shadow.headers.keys() {
if !primary.headers.contains_key(k) {
added.push(k.clone());
}
}
if added.is_empty() && removed.is_empty() && changed.is_empty() {
None
} else {
Some(HeaderDiff {
added,
removed,
changed,
})
}
}
}
fn shared_prefix(a: &[u8], b: &[u8]) -> usize {
a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
}