use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes;
use itertools::Either;
use maplit::*;
use serde_json::{json, Value};
use pact_models::bodies::OptionalBody;
use pact_models::content_types::JSON;
use pact_models::provider_states::ProviderState;
use pact_models::v4::http_parts::HttpRequest;
use tracing::warn;
use crate::provider_client::make_state_change_request;
pub trait RequestFilterExecutor: Debug {
fn call(self: Arc<Self>, request: &HttpRequest) -> HttpRequest;
fn call_non_http(
&self,
request_body: &OptionalBody,
metadata: &HashMap<String, Either<Value, Bytes>>
) -> (OptionalBody, HashMap<String, Either<Value, Bytes>>);
}
#[derive(Debug, Clone)]
pub struct NullRequestFilterExecutor {
_private_field: (),
}
impl RequestFilterExecutor for NullRequestFilterExecutor {
fn call(self: Arc<Self>, _request: &HttpRequest) -> HttpRequest {
unimplemented!("NullRequestFilterExecutor should never be called")
}
fn call_non_http(
&self,
_body: &OptionalBody,
_metadata: &HashMap<String, Either<Value, Bytes>>
) -> (OptionalBody, HashMap<String, Either<Value, Bytes>>) {
unimplemented!("NullRequestFilterExecutor should never be called")
}
}
#[derive(Debug, Clone)]
pub struct ProviderStateError {
pub description: String,
pub interaction_id: Option<String>
}
impl Display for ProviderStateError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Provider state failed: {}{}", self.interaction_id.as_ref()
.map(|id| format!("(interaction_id: {}) ", id)).unwrap_or_default(), self.description)
}
}
impl std::error::Error for ProviderStateError {}
#[async_trait]
pub trait ProviderStateExecutor: Debug {
async fn call(
self: Arc<Self>,
interaction_id: Option<String>,
provider_state: &ProviderState,
setup: bool,
client: Option<&reqwest::Client>
) -> anyhow::Result<HashMap<String, Value>>;
fn teardown(self: &Self)-> bool;
}
#[derive(Debug, Clone)]
pub struct HttpRequestProviderStateExecutor {
pub state_change_url: Option<String>,
pub state_change_teardown: bool,
pub state_change_body: bool,
pub reties: u8
}
impl Default for HttpRequestProviderStateExecutor {
fn default() -> HttpRequestProviderStateExecutor {
HttpRequestProviderStateExecutor {
state_change_url: None,
state_change_teardown: false,
state_change_body: true,
reties: 3
}
}
}
#[async_trait]
impl ProviderStateExecutor for HttpRequestProviderStateExecutor {
async fn call(
self: Arc<Self>,
interaction_id: Option<String>,
provider_state: &ProviderState,
setup: bool,
client: Option<&reqwest::Client>
) -> anyhow::Result<HashMap<String, Value>> {
match &self.state_change_url {
Some(state_change_url) => {
let mut state_change_request = HttpRequest { method: "POST".to_string(), .. HttpRequest::default() };
if self.state_change_body {
let json_body = json!({
"state".to_string() : provider_state.name.clone(),
"params".to_string() : provider_state.params.clone(),
"action".to_string() : if setup {
"setup".to_string()
} else {
"teardown".to_string()
}
});
state_change_request.body = OptionalBody::Present(json_body.to_string().into(), Some(JSON.clone()), None);
state_change_request.headers = Some(hashmap!{ "Content-Type".to_string() => vec!["application/json".to_string()] });
} else {
let mut query = hashmap!{ "state".to_string() => vec![Some(provider_state.name.clone())] };
if setup {
query.insert("action".to_string(), vec![Some("setup".to_string())]);
} else {
query.insert("action".to_string(), vec![Some("teardown".to_string())]);
}
for (k, v) in provider_state.params.clone() {
query.insert(k, vec![match v {
Value::String(ref s) => Some(s.clone()),
_ => Some(v.to_string())
}]);
}
state_change_request.query = Some(query);
}
make_state_change_request(client.unwrap_or(&reqwest::Client::default()), &state_change_url, &state_change_request, self.reties).await
.map_err(|err| ProviderStateError { description: err.to_string(), interaction_id }.into())
},
None => {
if !provider_state.name.is_empty() && setup {
warn!("State Change ignored as there is no state change URL provided for interaction {}", interaction_id.unwrap_or_default());
}
Ok(hashmap!{})
}
}
}
fn teardown(
self: &Self,
) -> bool {
return self.state_change_teardown;
}
}