use super::{ComparisonOptions, ComparisonResult, IeMultiplicityMode, MessageDiff, OptionalIeMode};
use crate::error::PfcpError;
use crate::ie::IeType;
use crate::message::Message;
mod compare;
pub struct MessageComparator<'a> {
left: &'a dyn Message,
right: &'a dyn Message,
options: ComparisonOptions,
}
impl<'a> MessageComparator<'a> {
pub fn new(left: &'a dyn Message, right: &'a dyn Message) -> Self {
if left.msg_type() != right.msg_type() {
panic!(
"Cannot compare different message types: {:?} vs {:?}. Use new_unchecked() if this is intentional.",
left.msg_type(),
right.msg_type()
);
}
Self {
left,
right,
options: ComparisonOptions::default(),
}
}
pub fn new_unchecked(left: &'a dyn Message, right: &'a dyn Message) -> Self {
Self {
left,
right,
options: ComparisonOptions::default(),
}
}
pub fn ignore_sequence(mut self) -> Self {
self.options.ignore_sequence = true;
self
}
pub fn include_sequence(mut self) -> Self {
self.options.ignore_sequence = false;
self
}
pub fn ignore_seid(mut self) -> Self {
self.options.ignore_seid = true;
self
}
pub fn ignore_priority(mut self) -> Self {
self.options.ignore_priority = true;
self
}
pub fn ignore_all_header_fields(mut self) -> Self {
self.options.ignore_sequence = true;
self.options.ignore_seid = true;
self.options.ignore_priority = true;
self
}
pub fn ignore_timestamps(mut self) -> Self {
self.options.ignore_timestamps = true;
self
}
pub fn ignore_recovery_timestamp(mut self) -> Self {
self.options
.ignored_ie_types
.insert(IeType::RecoveryTimeStamp);
self
}
pub fn timestamp_tolerance_secs(mut self, seconds: u32) -> Self {
self.options.timestamp_tolerance_secs = Some(seconds);
self
}
pub fn ignore_ie_types(mut self, ie_types: &[IeType]) -> Self {
self.options.ignored_ie_types.extend(ie_types.iter());
self
}
pub fn focus_on_ie_types(mut self, ie_types: &[IeType]) -> Self {
self.options.focus_ie_types = Some(ie_types.iter().copied().collect());
self
}
pub fn clear_focus(mut self) -> Self {
self.options.focus_ie_types = None;
self
}
pub fn strict_ie_order(mut self) -> Self {
self.options.strict_ie_order = true;
self
}
pub fn unordered_ies(mut self) -> Self {
self.options.strict_ie_order = false;
self
}
pub fn ie_multiplicity_mode(mut self, mode: IeMultiplicityMode) -> Self {
self.options.ie_multiplicity_mode = mode;
self
}
pub fn optional_ie_mode(mut self, mode: OptionalIeMode) -> Self {
self.options.optional_ie_mode = mode;
self
}
pub fn ignore_missing_ies(mut self) -> Self {
self.options.optional_ie_mode = OptionalIeMode::IgnoreMissing;
self
}
pub fn deep_compare_grouped_ies(mut self) -> Self {
self.options.deep_compare_grouped = true;
self
}
pub fn shallow_compare_grouped_ies(mut self) -> Self {
self.options.deep_compare_grouped = false;
self
}
pub fn semantic_comparison_for(mut self, ie_type: IeType) -> Self {
self.options.semantic_ie_types.insert(ie_type);
self
}
pub fn semantic_mode(mut self) -> Self {
self.options.use_semantic_comparison = true;
self
}
pub fn with_detailed_diff(mut self) -> Self {
self.options.generate_diff = true;
self
}
pub fn max_differences(mut self, max: usize) -> Self {
self.options.max_reported_differences = Some(max);
self
}
pub fn include_payload_in_diff(mut self) -> Self {
self.options.include_payload_in_diff = true;
self
}
pub fn test_mode(self) -> Self {
self.ignore_sequence()
.ignore_timestamps()
.ignore_priority()
.unordered_ies()
}
pub fn strict_mode(self) -> Self {
self.include_sequence()
.strict_ie_order()
.optional_ie_mode(OptionalIeMode::Strict)
}
pub fn semantic_preset(self) -> Self {
self.ignore_all_header_fields()
.ignore_timestamps()
.unordered_ies()
.semantic_mode()
.ignore_missing_ies()
}
pub fn audit_mode(self) -> Self {
self.ignore_recovery_timestamp()
.timestamp_tolerance_secs(5)
.unordered_ies()
}
pub fn compare(self) -> Result<ComparisonResult, PfcpError> {
compare::execute_comparison(self.left, self.right, &self.options)
}
pub fn diff(mut self) -> Result<MessageDiff, PfcpError> {
self.options.generate_diff = true;
let result = compare::execute_comparison(self.left, self.right, &self.options)?;
Ok(result.into_diff())
}
pub fn matches(self) -> Result<bool, PfcpError> {
let result = compare::execute_comparison(self.left, self.right, &self.options)?;
Ok(result.is_match())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::heartbeat_request::HeartbeatRequestBuilder;
use std::time::SystemTime;
#[test]
fn test_new_same_type() {
let msg1 = HeartbeatRequestBuilder::new(100)
.recovery_time_stamp(SystemTime::now())
.build();
let msg2 = HeartbeatRequestBuilder::new(200)
.recovery_time_stamp(SystemTime::now())
.build();
let _comparator = MessageComparator::new(&msg1, &msg2);
}
#[test]
fn test_builder_chaining() {
let msg1 = HeartbeatRequestBuilder::new(100)
.recovery_time_stamp(SystemTime::now())
.build();
let msg2 = HeartbeatRequestBuilder::new(200)
.recovery_time_stamp(SystemTime::now())
.build();
let comparator = MessageComparator::new(&msg1, &msg2)
.ignore_sequence()
.ignore_timestamps()
.unordered_ies()
.with_detailed_diff();
assert!(comparator.options.ignore_sequence);
assert!(comparator.options.ignore_timestamps);
assert!(!comparator.options.strict_ie_order);
assert!(comparator.options.generate_diff);
}
#[test]
fn test_preset_test_mode() {
let msg1 = HeartbeatRequestBuilder::new(100)
.recovery_time_stamp(SystemTime::now())
.build();
let msg2 = HeartbeatRequestBuilder::new(200)
.recovery_time_stamp(SystemTime::now())
.build();
let comparator = MessageComparator::new(&msg1, &msg2).test_mode();
assert!(comparator.options.ignore_sequence);
assert!(comparator.options.ignore_timestamps);
assert!(comparator.options.ignore_priority);
assert!(!comparator.options.strict_ie_order);
}
#[test]
fn test_preset_strict_mode() {
let msg1 = HeartbeatRequestBuilder::new(100)
.recovery_time_stamp(SystemTime::now())
.build();
let msg2 = HeartbeatRequestBuilder::new(200)
.recovery_time_stamp(SystemTime::now())
.build();
let comparator = MessageComparator::new(&msg1, &msg2).strict_mode();
assert!(!comparator.options.ignore_sequence);
assert!(comparator.options.strict_ie_order);
assert_eq!(comparator.options.optional_ie_mode, OptionalIeMode::Strict);
}
}