use std::panic::RefUnwindSafe;
use itertools::Itertools;
use pact_models::matchingrules::{MatchingRuleCategory, RuleList};
use pact_models::path_exp::DocPath;
use pact_models::prelude::v4::{SynchronousHttp, V4Pact};
use pact_models::v4::interaction::V4Interaction;
#[derive(Copy, Clone, Debug)]
pub struct MatchingConfiguration {
pub allow_unexpected_entries: bool,
pub log_executed_plan: bool,
pub log_plan_summary: bool,
pub coloured_output: bool,
pub show_types_in_errors: bool
}
impl MatchingConfiguration {
pub fn init_from_env() -> Self {
let mut config = MatchingConfiguration::default();
if let Some(val) = env_var_set("PACT_V2_MATCHING_LOG_EXECUTED_PLAN") {
config.log_executed_plan = val;
}
if let Some(val) = env_var_set("PACT_V2_MATCHING_LOG_PLAN_SUMMARY") {
config.log_plan_summary = val;
}
if let Some(val) = env_var_set("PACT_V2_MATCHING_COLOURED_OUTPUT") {
config.coloured_output = val;
}
config
}
}
fn env_var_set(name: &str) -> Option<bool> {
std::env::var(name)
.ok()
.map(|v| ["true", "1"].contains(&v.to_lowercase().as_str()))
}
impl Default for MatchingConfiguration {
fn default() -> Self {
MatchingConfiguration {
allow_unexpected_entries: false,
log_executed_plan: false,
log_plan_summary: true,
coloured_output: true,
show_types_in_errors: false
}
}
}
#[derive(Clone, Debug)]
pub struct PlanMatchingContext {
pub pact: V4Pact,
pub interaction: Box<dyn V4Interaction + Send + Sync + RefUnwindSafe>,
pub matching_rules: MatchingRuleCategory,
pub config: MatchingConfiguration
}
impl Default for PlanMatchingContext {
fn default() -> Self {
PlanMatchingContext {
pact: Default::default(),
interaction: Box::new(SynchronousHttp::default()),
matching_rules: Default::default(),
config: Default::default()
}
}
}
impl PlanMatchingContext {
pub fn matcher_is_defined(&self, path: &DocPath) -> bool {
let path = path.to_vec();
let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
self.matching_rules.matcher_is_defined(path_slice.as_slice())
}
pub fn select_best_matcher(&self, path: &DocPath) -> RuleList {
let path = path.to_vec();
let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
self.matching_rules.select_best_matcher(path_slice.as_slice())
}
pub fn select_best_matcher_from(&self, path1: &DocPath, path2: &DocPath) -> RuleList {
let path1_tokens = path1.to_vec();
let path1_list = path1_tokens.iter()
.map(|s| s.as_str())
.collect_vec();
let mut result1 = self.matching_rules.rules.iter()
.map(|(k, v)| (k, v, k.path_weight(&path1_list)))
.filter(|&(_, _, (w, _))| w > 0)
.collect_vec();
let path2_tokens = path2.to_vec();
let path2_list = path2_tokens
.iter()
.map(|s| s.as_str())
.collect_vec();
let result2 = self.matching_rules.rules.iter()
.map(|(k, v)| (k, v, k.path_weight(&path2_list)))
.filter(|&(_, _, (w, _))| w > 0)
.collect_vec();
result1.extend_from_slice(&result2);
result1.iter()
.max_by_key(|&(_, _, (w, t))| w * t)
.map(|(_, v, (_, t))| v.as_cascaded(*t != path1_list.len()))
.unwrap_or_default()
}
pub fn type_matcher_defined(&self, path: &DocPath) -> bool {
let path = path.to_vec();
let path_slice = path.iter().map(|p| p.as_str()).collect_vec();
self.matching_rules.resolve_matchers_for_path(path_slice.as_slice()).type_matcher_defined()
}
pub fn for_method(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.request.matching_rules.rules_for_category("method").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_path(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.request.matching_rules.rules_for_category("path").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_query(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.request.matching_rules.rules_for_category("query").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_headers(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.request.matching_rules.rules_for_category("header").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_body(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.request.matching_rules.rules_for_category("body").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: MatchingConfiguration {
show_types_in_errors: true,
.. self.config
}
}
}
pub fn for_status(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.response.matching_rules.rules_for_category("status").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_resp_headers(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.response.matching_rules.rules_for_category("header").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
pub fn for_resp_body(&self) -> Self {
let matching_rules = if let Some(req_res) = self.interaction.as_v4_http() {
req_res.response.matching_rules.rules_for_category("body").unwrap_or_default()
} else {
MatchingRuleCategory::default()
};
PlanMatchingContext {
pact: self.pact.clone(),
interaction: self.interaction.boxed_v4(),
matching_rules,
config: self.config
}
}
}