use std::sync::{Arc, atomic::AtomicU16};
use actix_router::{Path, ResourceDef};
use actix_web::http::StatusCode;
use serde::{Deserialize, Serialize};
use crate::{
ApateConfig, ApateCounters, RequestContext, ResourceRef,
matchers::{Matcher, matchers_and},
output::OutputType,
processors::Processor,
rhai::RhaiState,
};
pub const DEFAULT_RESPONSE_CODE: StatusCode = StatusCode::OK;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Deceit {
pub uris: Vec<String>,
#[serde(default)]
pub headers: Vec<(String, String)>,
#[serde(default)]
pub matchers: Vec<Matcher>,
#[serde(default)]
pub processors: Vec<Processor>,
#[serde(default)]
pub responses: Vec<DeceitResponse>,
}
impl Deceit {
pub fn match_againtst_uris(&self, request_path: &str) -> Option<Path<String>> {
log::debug!(
"Checking path: {request_path} against deceit URIs: {:?}",
self.uris
);
let mut path = Path::new(request_path.to_string());
let resource = ResourceDef::new(self.uris.clone());
if resource.capture_match_info(&mut path) {
Some(path)
} else {
None
}
}
pub fn match_response(
&self,
rref: &ResourceRef,
ctx: &RequestContext,
rhai: &RhaiState,
) -> Option<usize> {
if !matchers_and(rref, rhai, ctx, &self.matchers) {
return None;
}
for (idx, dr) in self.responses.iter().enumerate() {
if dr.matchers.is_empty() {
return Some(idx);
}
let deceit_ref = rref.with_level(idx);
if matchers_and(&deceit_ref, rhai, ctx, &dr.matchers) {
return Some(idx);
}
}
None
}
}
#[derive(Clone)]
pub struct DeceitResponseContext {
pub req: RequestContext,
pub response_code: Arc<AtomicU16>,
pub counters: ApateCounters,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct DeceitResponse {
#[serde(default)]
pub code: Option<u16>,
#[serde(default)]
pub matchers: Vec<Matcher>,
#[serde(default)]
pub headers: Vec<(String, String)>,
#[serde(default)]
pub processors: Vec<Processor>,
#[serde(default, rename = "type")]
pub output_type: OutputType,
#[serde(default)]
pub output: String,
}
pub fn create_response_context(
ctx: RequestContext,
cnt: ApateCounters,
) -> color_eyre::Result<DeceitResponseContext> {
Ok(DeceitResponseContext {
req: ctx.clone(),
response_code: Arc::new(AtomicU16::new(0)),
counters: cnt,
})
}
pub struct DeceitBuilder {
uris: Vec<String>,
headers: Vec<(String, String)>,
matchers: Vec<Matcher>,
processors: Vec<Processor>,
responses: Vec<DeceitResponse>,
}
impl DeceitBuilder {
pub fn with_uris<T: AsRef<str>>(uris: &[T]) -> Self {
let uris = uris.iter().map(|u| u.as_ref().to_string()).collect();
Self {
uris,
headers: Vec::new(),
matchers: Vec::new(),
responses: Vec::new(),
processors: Vec::new(),
}
}
pub fn build(self) -> Deceit {
Deceit {
uris: self.uris,
headers: self.headers,
matchers: self.matchers,
processors: self.processors,
responses: self.responses,
}
}
pub fn to_app_config(self) -> ApateConfig {
ApateConfig {
specs: crate::ApateSpecs {
deceit: vec![self.build()],
..Default::default()
},
..Default::default()
}
}
pub fn to_app_config_with_port(self, port: u16) -> ApateConfig {
ApateConfig {
port,
specs: crate::ApateSpecs {
deceit: vec![self.build()],
..Default::default()
},
..Default::default()
}
}
pub fn add_header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self
}
pub fn add_processor(mut self, processor: Processor) -> Self {
self.processors.push(processor);
self
}
pub fn with_responses(mut self, responses: Vec<DeceitResponse>) -> Self {
self.responses = responses;
self
}
pub fn add_response(mut self, response: DeceitResponse) -> Self {
self.responses.push(response);
self
}
pub fn add_matcher(mut self, matcher: Matcher) -> Self {
self.matchers.push(matcher);
self
}
pub fn require_method(mut self, http_method: &str) -> Self {
self.matchers.push(Matcher::Method {
eq: http_method.to_string(),
negate: false,
});
self
}
pub fn require_header(mut self, key: &str, value: &str) -> Self {
self.matchers.push(Matcher::Header {
key: key.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_query_arg(mut self, name: &str, value: &str) -> Self {
self.matchers.push(Matcher::QueryArg {
name: name.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_path_arg(mut self, name: &str, value: &str) -> Self {
self.matchers.push(Matcher::PathArg {
name: name.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_json_match(mut self, json_path: &str, eq: &str) -> Self {
self.matchers.push(Matcher::Json {
path: json_path.to_string(),
eq: eq.to_string(),
negate: false,
});
self
}
pub fn with_matchers(mut self, matchers: Vec<Matcher>) -> Self {
self.matchers = matchers;
self
}
}
#[derive(Default)]
pub struct DeceitResponseBuilder {
code: Option<u16>,
matchers: Vec<Matcher>,
headers: Vec<(String, String)>,
processors: Vec<Processor>,
output_type: OutputType,
output: String,
}
impl DeceitResponseBuilder {
pub fn build(self) -> DeceitResponse {
DeceitResponse {
code: self.code,
matchers: self.matchers,
headers: self.headers,
processors: self.processors,
output_type: self.output_type,
output: self.output,
}
}
pub fn code(mut self, code: u16) -> Self {
self.code = Some(code);
self
}
pub fn add_header(mut self, key: &str, value: &str) -> Self {
self.headers.push((key.to_string(), value.to_string()));
self
}
pub fn add_processor(mut self, processor: Processor) -> Self {
self.processors.push(processor);
self
}
pub fn with_output(mut self, output: &str) -> Self {
self.output = output.to_string();
self
}
pub fn with_output_type(mut self, output_type: OutputType) -> Self {
self.output_type = output_type;
self
}
pub fn add_matcher(mut self, matcher: Matcher) -> Self {
self.matchers.push(matcher);
self
}
pub fn require_method(mut self, http_method: &str) -> Self {
self.matchers.push(Matcher::Method {
eq: http_method.to_string(),
negate: false,
});
self
}
pub fn require_header(mut self, key: &str, value: &str) -> Self {
self.matchers.push(Matcher::Header {
key: key.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_query_arg(mut self, name: &str, value: &str) -> Self {
self.matchers.push(Matcher::QueryArg {
name: name.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_path_arg(mut self, name: &str, value: &str) -> Self {
self.matchers.push(Matcher::PathArg {
name: name.to_string(),
value: value.to_string(),
negate: false,
});
self
}
pub fn require_json_match(mut self, json_path: &str, eq: &str) -> Self {
self.matchers.push(Matcher::Json {
path: json_path.to_string(),
eq: eq.to_string(),
negate: false,
});
self
}
pub fn with_matchers(mut self, matchers: Vec<Matcher>) -> Self {
self.matchers = matchers;
self
}
}