use std::collections::HashMap;
use bytes::Bytes;
use maplit::*;
use pact_models::bodies::OptionalBody;
use pact_models::generators::Generators;
#[cfg(feature = "plugins")] use pact_models::http_parts::HttpPart;
use pact_models::matchingrules::MatchingRules;
use pact_models::prelude::ContentType;
use pact_models::response::Response;
use pact_models::v4::http_parts::{body_from_json, HttpResponse};
use pact_models::v4::interaction::InteractionMarkup;
#[cfg(feature = "plugins")] use pact_plugin_driver::catalogue_manager::find_content_matcher;
#[cfg(feature = "plugins")] use pact_plugin_driver::content::PluginConfiguration;
use serde_json::Value;
#[allow(unused_imports)] use tracing::debug;
use crate::prelude::*;
#[cfg(not(feature = "plugins"))]
#[derive(Clone, Debug)]
struct PluginConfiguration {}
#[derive(Clone, Debug)]
pub struct ResponseBuilder {
response: HttpResponse,
#[allow(dead_code)] pub(crate) plugin_config: HashMap<String, PluginConfiguration>,
interaction_markup: InteractionMarkup
}
impl ResponseBuilder {
pub fn status(&mut self, status: u16) -> &mut Self {
self.response.status = status;
self
}
pub fn ok(&mut self) -> &mut Self {
self.status(200)
}
pub fn created(&mut self) -> &mut Self {
self.status(201)
}
pub fn no_content(&mut self) -> &mut Self {
self.status(204)
}
pub fn unauthorized(&mut self) -> &mut Self {
self.status(401)
}
pub fn forbidden(&mut self) -> &mut Self {
self.status(403)
}
pub fn not_found(&mut self) -> &mut Self {
self.status(404)
}
pub fn build(&self) -> Response {
self.response.as_v3_response()
}
pub fn build_v4(&self) -> HttpResponse {
self.response.clone()
}
fn setup_core_matcher(&mut self, content_type: &ContentType, definition: Value) {
match definition {
Value::String(s) => self.response.body = OptionalBody::Present(Bytes::from(s), Some(content_type.clone()), None),
Value::Object(ref o) => if o.contains_key("contents") {
self.response.body = body_from_json(&definition, "contents", &None);
}
_ => {}
}
}
pub async fn contents(&mut self, content_type: ContentType, definition: Value) -> &mut Self {
#[cfg(feature = "plugins")]
{
match find_content_matcher(&content_type) {
Some(matcher) => {
debug!("Found a matcher for '{}': {:?}", content_type, matcher);
if matcher.is_core() {
debug!("Matcher is from the core framework");
self.setup_core_matcher(&content_type, definition);
} else {
let response = &mut self.response;
debug!("Plugin matcher, will get the plugin to provide the response contents");
match definition {
Value::Object(attributes) => {
let map = attributes.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
match matcher.configure_interaction(&content_type, map).await {
Ok((contents, plugin_config)) => {
debug!("Interaction contents = {:?}", contents);
debug!("Interaction plugin_config = {:?}", plugin_config);
let response_contents = contents.iter()
.filter(|interaction| interaction.part_name == "response")
.next()
.or_else(|| contents.iter()
.filter(|interaction| interaction.part_name == "")
.next());
if let Some(contents) = response_contents {
response.body = contents.body.clone();
if !response.has_header("content-type") {
response.add_header("content-type", vec![content_type.to_string().as_str()]);
}
if let Some(rules) = &contents.rules {
response.matching_rules.add_rules("body", rules.clone());
}
if let Some(generators) = &contents.generators {
response.generators.add_generators(generators.clone());
}
if !contents.plugin_config.is_empty() {
let plugin_config = PluginConfiguration {
interaction_configuration: hashmap!{
"response".to_string() => Value::Object(
contents.plugin_config.interaction_configuration
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
)
},
pact_configuration: contents.plugin_config.pact_configuration.clone(),
};
self.plugin_config.insert(matcher.plugin_name(), plugin_config);
}
self.interaction_markup = InteractionMarkup {
markup: contents.interaction_markup.clone(),
markup_type: contents.interaction_markup_type.clone()
};
}
if let Some(plugin_config) = plugin_config {
let plugin_name = matcher.plugin_name();
if self.plugin_config.contains_key(&*plugin_name) {
let entry = self.plugin_config.get_mut(&*plugin_name).unwrap();
for (k, v) in plugin_config.pact_configuration {
entry.pact_configuration.insert(k.clone(), v.clone());
}
} else {
self.plugin_config.insert(plugin_name.to_string(), plugin_config.clone());
}
}
}
Err(err) => panic!("Failed to call out to plugin - {}", err)
}
}
_ => panic!("{} is not a valid value for contents", definition)
}
}
}
None => {
debug!("No matcher was found, will default to the core framework");
self.setup_core_matcher(&content_type, definition);
}
}
}
#[cfg(not(feature = "plugins"))]
{
self.setup_core_matcher(&content_type, definition);
}
self
}
#[cfg(feature = "plugins")]
pub async fn contents_for_plugin<B: PluginInteractionBuilder>(&mut self, content_type: ContentType, builder: B) -> &mut Self {
self.contents(content_type, builder.build()).await
}
#[cfg(feature = "plugins")]
pub(crate) fn plugin_config(&self) -> HashMap<String, PluginConfiguration> {
self.plugin_config.clone()
}
pub(crate) fn interaction_markup(&self) -> InteractionMarkup {
self.interaction_markup.clone()
}
}
impl Default for ResponseBuilder {
fn default() -> Self {
ResponseBuilder {
response: HttpResponse::default(),
plugin_config: Default::default(),
interaction_markup: Default::default()
}
}
}
impl HttpPartBuilder for ResponseBuilder {
fn headers_and_matching_rules_mut(&mut self) -> (&mut HashMap<String, Vec<String>>, &mut MatchingRules) {
(
self.response.headers.get_or_insert(hashmap!{}),
&mut self.response.matching_rules,
)
}
fn generators(&mut self) -> &mut Generators {
&mut self.response.generators
}
fn body_and_matching_rules_mut(&mut self) -> (&mut OptionalBody, &mut MatchingRules) {
(
&mut self.response.body,
&mut self.response.matching_rules,
)
}
}