use std::collections::BTreeMap;
use std::fmt::Display;
use std::net::ToSocketAddrs;
use serde_json::Value;
use crate::data::{HttpMockRequest, RequestRequirements};
use crate::server::matchers::comparators::ValueComparator;
use crate::server::matchers::sources::{MultiValueSource, ValueRefSource};
use crate::server::matchers::targets::{MultiValueTarget, ValueRefTarget, ValueTarget};
use crate::server::matchers::transformers::Transformer;
use crate::server::matchers::{diff_str, Matcher, Reason};
use crate::server::{Mismatch, Tokenizer};
pub(crate) struct SingleValueMatcher<S, T>
where
S: Display,
T: Display,
{
pub entity_name: &'static str,
pub source: Box<dyn ValueRefSource<S> + Send + Sync>,
pub target: Box<dyn ValueTarget<T> + Send + Sync>,
pub comparator: Box<dyn ValueComparator<S, T> + Send + Sync>,
pub transformer: Option<Box<dyn Transformer<T, T> + Send + Sync>>,
pub with_reason: bool,
pub diff_with: Option<Tokenizer>,
pub weight: usize,
}
impl<S, T> SingleValueMatcher<S, T>
where
S: Display,
T: Display,
{
fn find_unmatched<'a>(
&self,
req_value: &Option<T>,
mock_values: &Option<Vec<&'a S>>,
) -> Vec<&'a S> {
let mock_values = match mock_values {
None => return Vec::new(),
Some(mv) => mv.to_vec(),
};
let req_value = match req_value {
None => return mock_values,
Some(rv) => rv,
};
mock_values
.into_iter()
.filter(|e| !self.comparator.matches(e, req_value))
.collect()
}
}
impl<S, T> Matcher for SingleValueMatcher<S, T>
where
S: Display,
T: Display,
{
fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool {
let req_value = self.target.parse_from_request(req);
let mock_value = self.source.parse_from_mock(mock);
self.find_unmatched(&req_value, &mock_value).is_empty()
}
fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize {
let req_value = self.target.parse_from_request(req);
let mock_values = self.source.parse_from_mock(mock);
self.find_unmatched(&req_value, &mock_values)
.into_iter()
.map(|s| self.comparator.distance(&Some(s), &req_value.as_ref()))
.map(|d| d * self.weight)
.sum()
}
fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec<Mismatch> {
let req_value = self.target.parse_from_request(req);
let mock_value = self.source.parse_from_mock(mock);
self.find_unmatched(&req_value, &mock_value)
.into_iter()
.map(|mock_value| {
let mock_value = mock_value.to_string();
let req_value = req_value.as_ref().unwrap().to_string();
Mismatch {
title: format!("The {} does not match", self.entity_name),
reason: match self.with_reason {
true => Some(Reason {
expected: mock_value.to_owned(),
actual: req_value.to_owned(),
comparison: self.comparator.name().into(),
best_match: false,
}),
false => None,
},
diff: self.diff_with.map(|t| diff_str(&mock_value, &req_value, t)),
}
})
.collect()
}
}
pub(crate) struct MultiValueMatcher<SK, SV, TK, TV>
where
SK: Display,
SV: Display,
TK: Display,
TV: Display,
{
pub entity_name: &'static str,
pub source: Box<dyn MultiValueSource<SK, SV> + Send + Sync>,
pub target: Box<dyn MultiValueTarget<TK, TV> + Send + Sync>,
pub key_comparator: Box<dyn ValueComparator<SK, TK> + Send + Sync>,
pub value_comparator: Box<dyn ValueComparator<SV, TV> + Send + Sync>,
pub key_transformer: Option<Box<dyn Transformer<SK, SK> + Send + Sync>>,
pub value_transformer: Option<Box<dyn Transformer<SV, SV> + Send + Sync>>,
pub with_reason: bool,
pub diff_with: Option<Tokenizer>,
pub weight: usize,
}
impl<SK, SV, TK, TV> MultiValueMatcher<SK, SV, TK, TV>
where
SK: Display,
SV: Display,
TK: Display,
TV: Display,
{
fn find_unmatched<'a>(
&self,
req_values: &Vec<(TK, Option<TV>)>,
mock_values: &'a Vec<(&'a SK, Option<&'a SV>)>,
) -> Vec<&'a (&'a SK, Option<&'a SV>)> {
mock_values
.into_iter()
.filter(|(sk, sv)| {
req_values
.iter()
.find(|(tk, tv)| {
let key_matches = self.key_comparator.matches(sk, &tk);
let value_matches = match (sv, tv) {
(Some(_), None) => false, (Some(sv), Some(tv)) => self.value_comparator.matches(sv, &tv),
_ => true,
};
key_matches && value_matches
})
.is_none()
})
.collect()
}
fn find_best_match<'a>(
&self,
sk: &SK,
sv: &Option<&SV>,
req_values: &'a Vec<(TK, Option<TV>)>,
) -> Option<(&'a TK, &'a Option<TV>)> {
if req_values.is_empty() {
return None;
}
let found = req_values
.into_iter()
.find(|(k, v)| k.to_string().eq(&sk.to_string()));
if let Some((fk, fv)) = found {
return Some((fk, fv));
}
req_values
.into_iter()
.map(|(tk, tv)| {
let key_distance = self.key_comparator.distance(&Some(sk), &Some(tk));
let value_distance = self.value_comparator.distance(&sv, &tv.as_ref());
(tk, tv, key_distance + value_distance)
})
.min_by(|(_, _, d1), (_, _, d2)| d1.cmp(d2))
.map(|(k, v, _)| (k.to_owned(), v.to_owned()))
}
}
impl<SK, SV, TK, TV> Matcher for MultiValueMatcher<SK, SV, TK, TV>
where
SK: Display,
SV: Display,
TK: Display,
TV: Display,
{
fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool {
let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new());
let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new());
self.find_unmatched(&req_values, &mock_values).is_empty()
}
fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize {
let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new());
let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new());
self.find_unmatched(&req_values, &mock_values)
.into_iter()
.map(|(k, v)| (k, v, self.find_best_match(&k, v, &req_values)))
.map(|(k, v, best_match)| match best_match {
None => {
self.key_comparator.distance(&Some(k), &None)
+ self.value_comparator.distance(v, &None)
}
Some((bmk, bmv)) => {
self.key_comparator.distance(&Some(k), &Some(bmk))
+ self.value_comparator.distance(v, &bmv.as_ref())
}
})
.map(|d| d * self.weight)
.sum()
}
fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec<Mismatch> {
let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new());
let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new());
self.find_unmatched(&req_values, &mock_values)
.into_iter()
.map(|(k, v)| (k, v, self.find_best_match(&k, v, &req_values)))
.map(|(k, v, best_match)| Mismatch {
title: match v {
None => format!("Expected {} with name '{}' to be present in the request but it wasn't.", self.entity_name, &k),
Some(v) => format!("Expected {} with name '{}' and value '{}' to be present in the request but it wasn't.", self.entity_name, &k, v),
},
reason: best_match.as_ref().map(|(bmk, bmv)| {
Reason {
expected: match v {
None => format!("{}", k),
Some(v) => format!("{}={}", k, v),
},
actual: match bmv {
None => format!("{}", bmk),
Some(bmv) => format!("{}={}", bmk, bmv),
},
comparison: format!("key={}, value={}", self.key_comparator.name(), self.value_comparator.name()),
best_match: true,
}
}),
diff: None,
})
.collect()
}
}
pub(crate) struct FunctionValueMatcher<S, T> {
pub entity_name: &'static str,
pub source: Box<dyn ValueRefSource<S> + Send + Sync>,
pub target: Box<dyn ValueRefTarget<T> + Send + Sync>,
pub comparator: Box<dyn ValueComparator<S, T> + Send + Sync>,
pub transformer: Option<Box<dyn Transformer<T, T> + Send + Sync>>,
pub weight: usize,
}
impl<S, T> FunctionValueMatcher<S, T> {
fn get_unmatched<'a>(
&self,
req_value: &Option<&T>,
mock_values: &Option<Vec<&'a S>>,
) -> Vec<usize> {
let mock_values = match mock_values {
None => return Vec::new(),
Some(mv) => mv.to_vec(),
};
let req_value = match req_value {
None => {
return mock_values
.into_iter()
.enumerate()
.map(|(idx, _)| idx)
.collect()
}
Some(rv) => rv,
};
mock_values
.into_iter()
.enumerate()
.filter(|(idx, e)| !self.comparator.matches(e, req_value))
.map(|(idx, e)| (idx))
.collect()
}
}
impl<S, T> Matcher for FunctionValueMatcher<S, T> {
fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool {
let req_value = self.target.parse_from_request(req);
let mock_values = self.source.parse_from_mock(mock);
self.get_unmatched(&req_value, &mock_values).is_empty()
}
fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize {
let req_value = self.target.parse_from_request(req);
let mock_values = self.source.parse_from_mock(mock);
self.get_unmatched(&req_value, &mock_values).len() * self.weight
}
fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec<Mismatch> {
let req_value = self.target.parse_from_request(req);
let mock_value = self.source.parse_from_mock(mock);
self.get_unmatched(&req_value, &mock_value)
.into_iter()
.map(|idx| Mismatch {
title: format!(
"The {} at position {} does not match",
self.entity_name,
idx + 1
),
reason: None,
diff: None,
})
.collect()
}
}
#[cfg(test)]
mod test {
#[test]
fn todo() {}
}