request_shadow/
divergence.rs1use crate::backend::ResponseRecord;
4use crate::config::{IgnoreField, ShadowConfig};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct Divergence {
9 pub status: Option<(u16, u16)>,
11 pub headers: Option<HeaderDiff>,
13 pub body: Option<BodyDiff>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct HeaderDiff {
20 pub added: Vec<String>,
22 pub removed: Vec<String>,
24 pub changed: Vec<String>,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct BodyDiff {
31 pub primary_len: usize,
33 pub shadow_len: usize,
35 pub prefix_equal_bytes: usize,
37}
38
39impl Divergence {
40 pub fn compare(
43 primary: &ResponseRecord,
44 shadow: &ResponseRecord,
45 config: &ShadowConfig,
46 ) -> Option<Self> {
47 let status =
48 if config.ignore.contains(&IgnoreField::Status) || primary.status == shadow.status {
49 None
50 } else {
51 Some((primary.status, shadow.status))
52 };
53
54 let headers = if config.ignore.contains(&IgnoreField::Headers) {
55 None
56 } else {
57 Self::diff_headers(primary, shadow)
58 };
59
60 let body = if config.ignore.contains(&IgnoreField::Body) || primary.body == shadow.body {
61 None
62 } else {
63 Some(BodyDiff {
64 primary_len: primary.body.len(),
65 shadow_len: shadow.body.len(),
66 prefix_equal_bytes: shared_prefix(&primary.body, &shadow.body),
67 })
68 };
69
70 if status.is_none() && headers.is_none() && body.is_none() {
71 None
72 } else {
73 Some(Self {
74 status,
75 headers,
76 body,
77 })
78 }
79 }
80
81 fn diff_headers(primary: &ResponseRecord, shadow: &ResponseRecord) -> Option<HeaderDiff> {
82 let mut added = Vec::new();
83 let mut removed = Vec::new();
84 let mut changed = Vec::new();
85
86 for (k, v) in &primary.headers {
87 match shadow.headers.get(k) {
88 None => removed.push(k.clone()),
89 Some(sv) if sv != v => changed.push(k.clone()),
90 _ => {}
91 }
92 }
93 for k in shadow.headers.keys() {
94 if !primary.headers.contains_key(k) {
95 added.push(k.clone());
96 }
97 }
98 if added.is_empty() && removed.is_empty() && changed.is_empty() {
99 None
100 } else {
101 Some(HeaderDiff {
102 added,
103 removed,
104 changed,
105 })
106 }
107 }
108}
109
110fn shared_prefix(a: &[u8], b: &[u8]) -> usize {
111 a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
112}