use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_string_parameters: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<Body>,
}
impl HttpRequest {
pub fn new() -> Self {
Self::default()
}
pub fn method(mut self, method: impl Into<String>) -> Self {
self.method = Some(method.into());
self
}
pub fn path(mut self, path: impl Into<String>) -> Self {
self.path = Some(path.into());
self
}
pub fn query_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let params = self.query_string_parameters.get_or_insert_with(HashMap::new);
params
.entry(key.into())
.or_default()
.push(value.into());
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let headers = self.headers.get_or_insert_with(HashMap::new);
headers
.entry(key.into())
.or_default()
.push(value.into());
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(Body::Plain(body.into()));
self
}
pub fn json_body(mut self, json: serde_json::Value) -> Self {
self.body = Some(Body::Typed {
body_type: "JSON".to_string(),
json: json.to_string(),
});
self
}
pub fn file_body(mut self, file_path: impl Into<String>) -> Self {
self.body = Some(Body::File {
file_path: file_path.into(),
content_type: None,
template_type: None,
});
self
}
pub fn body_value(mut self, body: Body) -> Self {
self.body = Some(body);
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Body {
Plain(String),
Typed { body_type: String, json: String },
File {
file_path: String,
content_type: Option<String>,
template_type: Option<String>,
},
}
impl Body {
pub fn file(file_path: impl Into<String>) -> Self {
Body::File {
file_path: file_path.into(),
content_type: None,
template_type: None,
}
}
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
if let Body::File {
content_type: ref mut ct,
..
} = self
{
*ct = Some(content_type.into());
}
self
}
pub fn with_template_type(mut self, template_type: impl Into<String>) -> Self {
if let Body::File {
template_type: ref mut tt,
..
} = self
{
*tt = Some(template_type.into());
}
self
}
}
impl Serialize for Body {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Body::Plain(s) => serializer.serialize_str(s),
Body::Typed { body_type, json } => {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", body_type)?;
map.serialize_entry("json", json)?;
map.end()
}
Body::File {
file_path,
content_type,
template_type,
} => {
use serde::ser::SerializeMap;
let count = 2
+ content_type.as_ref().map_or(0, |_| 1)
+ template_type.as_ref().map_or(0, |_| 1);
let mut map = serializer.serialize_map(Some(count))?;
map.serialize_entry("type", "FILE")?;
map.serialize_entry("filePath", file_path)?;
if let Some(ct) = content_type {
map.serialize_entry("contentType", ct)?;
}
if let Some(tt) = template_type {
map.serialize_entry("templateType", tt)?;
}
map.end()
}
}
}
}
impl<'de> Deserialize<'de> for Body {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde_json::Value;
let v = Value::deserialize(deserializer)?;
match v {
Value::String(s) => Ok(Body::Plain(s)),
Value::Object(map) => {
let body_type = map
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("JSON")
.to_string();
if body_type == "FILE" {
let file_path = map
.get("filePath")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let content_type = map
.get("contentType")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let template_type = map
.get("templateType")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(Body::File {
file_path,
content_type,
template_type,
})
} else {
let json = map
.get("json")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
Ok(Body::Typed { body_type, json })
}
}
_ => Ok(Body::Plain(v.to_string())),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub status_code: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl HttpResponse {
pub fn new() -> Self {
Self::default()
}
pub fn status_code(mut self, code: u16) -> Self {
self.status_code = Some(code);
self
}
pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let headers = self.headers.get_or_insert_with(HashMap::new);
headers
.entry(key.into())
.or_default()
.push(value.into());
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
pub fn delay(mut self, delay: Delay) -> Self {
self.delay = Some(delay);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpTemplate {
#[serde(skip_serializing_if = "Option::is_none")]
pub template_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_file: Option<String>,
}
impl HttpTemplate {
pub fn new(template_type: impl Into<String>, template: impl Into<String>) -> Self {
Self {
template_type: Some(template_type.into()),
template: Some(template.into()),
template_file: None,
}
}
pub fn from_file(template_type: impl Into<String>, file_path: impl Into<String>) -> Self {
Self {
template_type: Some(template_type.into()),
template: None,
template_file: Some(file_path.into()),
}
}
pub fn template_type(mut self, template_type: impl Into<String>) -> Self {
self.template_type = Some(template_type.into());
self
}
pub fn template(mut self, template: impl Into<String>) -> Self {
self.template = Some(template.into());
self
}
pub fn template_file(mut self, file_path: impl Into<String>) -> Self {
self.template_file = Some(file_path.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpForward {
pub host: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<String>,
}
impl HttpForward {
pub fn new(host: impl Into<String>, port: u16) -> Self {
Self {
host: host.into(),
port: Some(port),
scheme: None,
}
}
pub fn scheme(mut self, scheme: impl Into<String>) -> Self {
self.scheme = Some(scheme.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpError {
#[serde(skip_serializing_if = "Option::is_none")]
pub drop_connection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_bytes: Option<String>,
}
impl HttpError {
pub fn new() -> Self {
Self::default()
}
pub fn drop_connection(mut self, drop: bool) -> Self {
self.drop_connection = Some(drop);
self
}
pub fn response_bytes(mut self, bytes: impl Into<String>) -> Self {
self.response_bytes = Some(bytes.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Delay {
pub time_unit: String,
pub value: u64,
}
impl Delay {
pub fn milliseconds(value: u64) -> Self {
Self {
time_unit: "MILLISECONDS".to_string(),
value,
}
}
pub fn seconds(value: u64) -> Self {
Self {
time_unit: "SECONDS".to_string(),
value,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Times {
#[serde(skip_serializing_if = "Option::is_none")]
pub remaining_times: Option<u32>,
pub unlimited: bool,
}
impl Times {
pub fn unlimited() -> Self {
Self {
remaining_times: None,
unlimited: true,
}
}
pub fn exactly(n: u32) -> Self {
Self {
remaining_times: Some(n),
unlimited: false,
}
}
pub fn once() -> Self {
Self::exactly(1)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TimeToLive {
#[serde(skip_serializing_if = "Option::is_none")]
pub time_unit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_to_live: Option<u64>,
pub unlimited: bool,
}
impl TimeToLive {
pub fn unlimited() -> Self {
Self {
time_unit: None,
time_to_live: None,
unlimited: true,
}
}
pub fn seconds(seconds: u64) -> Self {
Self {
time_unit: Some("SECONDS".to_string()),
time_to_live: Some(seconds),
unlimited: false,
}
}
pub fn milliseconds(millis: u64) -> Self {
Self {
time_unit: Some("MILLISECONDS".to_string()),
time_to_live: Some(millis),
unlimited: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VerificationTimes {
#[serde(skip_serializing_if = "Option::is_none")]
pub at_least: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub at_most: Option<u32>,
}
impl VerificationTimes {
pub fn at_least(n: u32) -> Self {
Self {
at_least: Some(n),
at_most: None,
}
}
pub fn at_most(n: u32) -> Self {
Self {
at_least: None,
at_most: Some(n),
}
}
pub fn exactly(n: u32) -> Self {
Self {
at_least: Some(n),
at_most: Some(n),
}
}
pub fn between(min: u32, max: u32) -> Self {
Self {
at_least: Some(min),
at_most: Some(max),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Expectation {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<i32>,
pub http_request: HttpRequest,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_response: Option<HttpResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward: Option<HttpForward>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_response_template: Option<HttpTemplate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward_template: Option<HttpTemplate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_error: Option<HttpError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub times: Option<Times>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_to_live: Option<TimeToLive>,
}
impl Expectation {
pub fn new(request: HttpRequest) -> Self {
Self {
http_request: request,
..Default::default()
}
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn priority(mut self, priority: i32) -> Self {
self.priority = Some(priority);
self
}
pub fn respond(mut self, response: HttpResponse) -> Self {
self.http_response = Some(response);
self
}
pub fn forward(mut self, forward: HttpForward) -> Self {
self.http_forward = Some(forward);
self
}
pub fn respond_template(mut self, template: HttpTemplate) -> Self {
self.http_response_template = Some(template);
self
}
pub fn forward_template(mut self, template: HttpTemplate) -> Self {
self.http_forward_template = Some(template);
self
}
pub fn error(mut self, error: HttpError) -> Self {
self.http_error = Some(error);
self
}
pub fn times(mut self, times: Times) -> Self {
self.times = Some(times);
self
}
pub fn time_to_live(mut self, ttl: TimeToLive) -> Self {
self.time_to_live = Some(ttl);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Verification {
#[serde(skip_serializing_if = "Option::is_none")]
pub http_request: Option<HttpRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_response: Option<HttpResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub times: Option<VerificationTimes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum_number_of_request_to_return_in_verification_failure: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VerificationSequence {
#[serde(skip_serializing_if = "Option::is_none")]
pub http_requests: Option<Vec<HttpRequest>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_responses: Option<Vec<HttpResponse>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Ports {
pub ports: Vec<u16>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetrieveType {
Requests,
ActiveExpectations,
RecordedExpectations,
Logs,
RequestResponses,
}
impl RetrieveType {
pub fn as_str(&self) -> &'static str {
match self {
RetrieveType::Requests => "REQUESTS",
RetrieveType::ActiveExpectations => "ACTIVE_EXPECTATIONS",
RetrieveType::RecordedExpectations => "RECORDED_EXPECTATIONS",
RetrieveType::Logs => "LOGS",
RetrieveType::RequestResponses => "REQUEST_RESPONSES",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RetrieveFormat {
Json,
LogEntries,
}
impl RetrieveFormat {
pub fn as_str(&self) -> &'static str {
match self {
RetrieveFormat::Json => "JSON",
RetrieveFormat::LogEntries => "LOG_ENTRIES",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClearType {
All,
Log,
Expectations,
}
impl ClearType {
pub fn as_str(&self) -> &'static str {
match self {
ClearType::All => "ALL",
ClearType::Log => "LOG",
ClearType::Expectations => "EXPECTATIONS",
}
}
}