use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub type Extra = serde_json::Map<String, serde_json::Value>;
fn one_or_many<'de, D, T>(deserializer: D) -> std::result::Result<Option<Vec<T>>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
let opt = Option::<OneOrMany<T>>::deserialize(deserializer)?;
Ok(opt.map(|v| match v {
OneOrMany::One(t) => vec![t],
OneOrMany::Many(v) => v,
}))
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ParameterValues {
Values(Vec<String>),
Matcher(serde_json::Value),
}
impl ParameterValues {
pub fn as_values(&self) -> Option<&[String]> {
match self {
ParameterValues::Values(v) => Some(v),
ParameterValues::Matcher(_) => None,
}
}
}
impl From<Vec<String>> for ParameterValues {
fn from(values: Vec<String>) -> Self {
ParameterValues::Values(values)
}
}
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jwt: Option<Jwt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub socket_address: Option<SocketAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub not: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secure: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_alive: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path_parameters: Option<HashMap<String, ParameterValues>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<HashMap<String, String>>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl HttpRequest {
pub fn new() -> Self {
Self::default()
}
pub fn socket_address(mut self, socket_address: SocketAddress) -> Self {
self.socket_address = Some(socket_address);
self
}
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
}
pub fn jwt(mut self, jwt: Jwt) -> Self {
self.jwt = Some(jwt);
self
}
pub fn not(mut self, not: bool) -> Self {
self.not = Some(not);
self
}
pub fn secure(mut self, secure: bool) -> Self {
self.secure = Some(secure);
self
}
pub fn keep_alive(mut self, keep_alive: bool) -> Self {
self.keep_alive = Some(keep_alive);
self
}
pub fn protocol(mut self, protocol: impl Into<String>) -> Self {
self.protocol = Some(protocol.into());
self
}
pub fn path_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let params = self.path_parameters.get_or_insert_with(HashMap::new);
match params
.entry(key.into())
.or_insert_with(|| ParameterValues::Values(Vec::new()))
{
ParameterValues::Values(v) => v.push(value.into()),
ParameterValues::Matcher(_) => {
}
}
self
}
pub fn cookie(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
let cookies = self.cookies.get_or_insert_with(HashMap::new);
cookies.insert(name.into(), value.into());
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>,
},
AllOf(Vec<Body>),
Matcher {
body_type: String,
value_key: String,
value: String,
},
Object(serde_json::Map<String, serde_json::Value>),
}
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
}
pub fn all_of(bodies: Vec<Body>) -> Self {
Body::AllOf(bodies)
}
pub fn json_path(expression: impl Into<String>) -> Self {
Body::Matcher {
body_type: "JSON_PATH".to_string(),
value_key: "jsonPath".to_string(),
value: expression.into(),
}
}
pub fn regex(pattern: impl Into<String>) -> Self {
Body::Matcher {
body_type: "REGEX".to_string(),
value_key: "regex".to_string(),
value: pattern.into(),
}
}
pub fn xpath(expression: impl Into<String>) -> Self {
Body::Matcher {
body_type: "XPATH".to_string(),
value_key: "xpath".to_string(),
value: expression.into(),
}
}
pub fn string(value: impl Into<String>, sub_string: bool) -> Self {
let mut map = serde_json::Map::new();
map.insert("type".into(), serde_json::Value::from("STRING"));
map.insert("string".into(), serde_json::Value::from(value.into()));
map.insert("subString".into(), serde_json::Value::from(sub_string));
Body::Object(map)
}
pub fn xml(value: impl Into<String>) -> Self {
Self::single_object("XML", "xml", value.into())
}
pub fn xml_schema(schema: impl Into<String>) -> Self {
Self::single_object("XML_SCHEMA", "xmlSchema", schema.into())
}
pub fn json_schema(schema: impl Into<String>) -> Self {
Self::single_object("JSON_SCHEMA", "jsonSchema", schema.into())
}
pub fn parameters(parameters: HashMap<String, Vec<String>>) -> Self {
let mut map = serde_json::Map::new();
map.insert("type".into(), serde_json::Value::from("PARAMETERS"));
map.insert(
"parameters".into(),
serde_json::to_value(parameters).unwrap_or(serde_json::Value::Null),
);
Body::Object(map)
}
pub fn binary(data: impl AsRef<[u8]>, content_type: Option<String>) -> Self {
let mut map = serde_json::Map::new();
map.insert("type".into(), serde_json::Value::from("BINARY"));
map.insert(
"base64Bytes".into(),
serde_json::Value::from(BASE64.encode(data.as_ref())),
);
if let Some(ct) = content_type {
map.insert("contentType".into(), serde_json::Value::from(ct));
}
Body::Object(map)
}
pub fn graphql(query: impl Into<String>) -> Self {
let mut map = serde_json::Map::new();
map.insert("type".into(), serde_json::Value::from("GRAPHQL"));
map.insert("query".into(), serde_json::Value::from(query.into()));
Body::Object(map)
}
pub fn wasm(object: serde_json::Map<String, serde_json::Value>) -> Self {
Body::Object(object)
}
pub fn object(object: serde_json::Map<String, serde_json::Value>) -> Self {
Body::Object(object)
}
fn single_object(body_type: &str, key: &str, value: String) -> Self {
let mut map = serde_json::Map::new();
map.insert("type".into(), serde_json::Value::from(body_type));
map.insert(key.into(), serde_json::Value::from(value));
Body::Object(map)
}
}
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()
}
Body::AllOf(bodies) => {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", "ALL_OF")?;
map.serialize_entry("bodyAllOf", bodies)?;
map.end()
}
Body::Matcher {
body_type,
value_key,
value,
} => {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(2))?;
map.serialize_entry("type", body_type)?;
map.serialize_entry(value_key.as_str(), value)?;
map.end()
}
Body::Object(object) => object.serialize(serializer),
}
}
}
fn matcher_key_value(
body_type: &str,
map: &serde_json::Map<String, serde_json::Value>,
) -> Option<(String, String)> {
let key = match body_type {
"JSON_PATH" => "jsonPath",
"REGEX" => "regex",
"XPATH" => "xpath",
_ => return None,
};
map.get(key)
.and_then(|v| v.as_str())
.map(|s| (key.to_string(), s.to_string()))
}
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 if body_type == "ALL_OF" {
let bodies = map
.get("bodyAllOf")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.cloned()
.map(serde_json::from_value)
.collect::<std::result::Result<Vec<Body>, _>>()
})
.transpose()
.map_err(serde::de::Error::custom)?
.unwrap_or_default();
Ok(Body::AllOf(bodies))
} else if map.len() == 2 && matcher_key_value(&body_type, &map).is_some() {
let (value_key, value) = matcher_key_value(&body_type, &map)
.expect("matcher_key_value checked above");
Ok(Body::Matcher {
body_type,
value_key,
value,
})
} else if body_type == "JSON"
&& map.len() == 2
&& map.get("json").is_some_and(|v| v.is_string())
{
let json = map
.get("json")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
Ok(Body::Typed { body_type, json })
} else {
Ok(Body::Object(map))
}
}
_ => Ok(Body::Plain(v.to_string())),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Jwt {
pub claims: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub issuer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<String>,
}
impl Jwt {
pub fn new() -> Self {
Self::default()
}
pub fn claim(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.claims.insert(name.into(), value.into());
self
}
pub fn claims(mut self, claims: HashMap<String, String>) -> Self {
self.claims = claims;
self
}
pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
self.issuer = Some(issuer.into());
self
}
pub fn audience(mut self, audience: impl Into<String>) -> Self {
self.audience = Some(audience.into());
self
}
pub fn algorithm(mut self, algorithm: impl Into<String>) -> Self {
self.algorithm = Some(algorithm.into());
self
}
pub fn header(mut self, header: impl Into<String>) -> Self {
self.header = Some(header.into());
self
}
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 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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cookies: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason_phrase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_code_range: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trailers: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_from_schema: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_options: Option<ConnectionOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recover_after: Option<RecoverAfter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
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
}
pub fn cookie(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
let cookies = self.cookies.get_or_insert_with(HashMap::new);
cookies.insert(name.into(), value.into());
self
}
pub fn reason_phrase(mut self, reason_phrase: impl Into<String>) -> Self {
self.reason_phrase = Some(reason_phrase.into());
self
}
pub fn status_code_range(mut self, range: impl Into<String>) -> Self {
self.status_code_range = Some(range.into());
self
}
pub fn trailer(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let trailers = self.trailers.get_or_insert_with(HashMap::new);
trailers.entry(key.into()).or_default().push(value.into());
self
}
pub fn generate_from_schema(mut self, schema: impl Into<String>) -> Self {
self.generate_from_schema = Some(schema.into());
self
}
pub fn connection_options(mut self, options: ConnectionOptions) -> Self {
self.connection_options = Some(options);
self
}
pub fn recover_after(mut self, recover_after: RecoverAfter) -> Self {
self.recover_after = Some(recover_after);
self
}
pub fn primary(mut self, primary: bool) -> Self {
self.primary = Some(primary);
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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl HttpForward {
pub fn new(host: impl Into<String>, port: u16) -> Self {
Self {
host: host.into(),
port: Some(port),
scheme: None,
delay: None,
}
}
pub fn scheme(mut self, scheme: impl Into<String>) -> Self {
self.scheme = Some(scheme.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 HttpClassCallback {
pub callback_class: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
}
impl HttpClassCallback {
pub fn new(callback_class: impl Into<String>) -> Self {
Self {
callback_class: callback_class.into(),
delay: None,
primary: None,
}
}
pub fn delay(mut self, delay: Delay) -> Self {
self.delay = Some(delay);
self
}
pub fn primary(mut self, primary: bool) -> Self {
self.primary = Some(primary);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpObjectCallback {
pub client_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_callback: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
}
impl HttpObjectCallback {
pub fn new(client_id: impl Into<String>) -> Self {
Self {
client_id: client_id.into(),
response_callback: None,
delay: None,
primary: None,
}
}
pub fn response_callback(mut self, response_callback: bool) -> Self {
self.response_callback = Some(response_callback);
self
}
pub fn delay(mut self, delay: Delay) -> Self {
self.delay = Some(delay);
self
}
pub fn primary(mut self, primary: bool) -> Self {
self.primary = Some(primary);
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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
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
}
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 SseEvent {
#[serde(skip_serializing_if = "Option::is_none")]
pub event: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl SseEvent {
pub fn new() -> Self {
Self::default()
}
pub fn event(mut self, event: impl Into<String>) -> Self {
self.event = Some(event.into());
self
}
pub fn data(mut self, data: impl Into<String>) -> Self {
self.data = Some(data.into());
self
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn retry(mut self, retry: u32) -> Self {
self.retry = Some(retry);
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 HttpSseResponse {
#[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 events: Option<Vec<SseEvent>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_connection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl HttpSseResponse {
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 event(mut self, event: SseEvent) -> Self {
self.events.get_or_insert_with(Vec::new).push(event);
self
}
pub fn events(mut self, events: Vec<SseEvent>) -> Self {
self.events = Some(events);
self
}
pub fn close_connection(mut self, close: bool) -> Self {
self.close_connection = Some(close);
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 WebSocketMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub binary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl WebSocketMessage {
pub fn text(text: impl Into<String>) -> Self {
Self {
text: Some(text.into()),
binary: None,
delay: None,
}
}
pub fn binary(data: impl AsRef<[u8]>) -> Self {
Self {
text: None,
binary: Some(BASE64.encode(data.as_ref())),
delay: None,
}
}
pub fn binary_base64(base64: impl Into<String>) -> Self {
Self {
text: None,
binary: Some(base64.into()),
delay: None,
}
}
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 WebSocketMatcher {
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text_matcher: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub responses: Option<Vec<WebSocketMessage>>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl WebSocketMatcher {
pub fn new() -> Self {
Self::default()
}
pub fn frame_type(mut self, frame_type: impl Into<String>) -> Self {
self.frame_type = Some(frame_type.into());
self
}
pub fn text_matcher(mut self, text_matcher: impl Into<String>) -> Self {
self.text_matcher = Some(text_matcher.into());
self
}
pub fn response(mut self, response: WebSocketMessage) -> Self {
self.responses.get_or_insert_with(Vec::new).push(response);
self
}
pub fn responses(mut self, responses: Vec<WebSocketMessage>) -> Self {
self.responses = Some(responses);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpWebSocketResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub subprotocol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<WebSocketMessage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub matchers: Option<Vec<WebSocketMatcher>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_connection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl HttpWebSocketResponse {
pub fn new() -> Self {
Self::default()
}
pub fn matcher(mut self, matcher: WebSocketMatcher) -> Self {
self.matchers.get_or_insert_with(Vec::new).push(matcher);
self
}
pub fn matchers(mut self, matchers: Vec<WebSocketMatcher>) -> Self {
self.matchers = Some(matchers);
self
}
pub fn subprotocol(mut self, subprotocol: impl Into<String>) -> Self {
self.subprotocol = Some(subprotocol.into());
self
}
pub fn message(mut self, message: WebSocketMessage) -> Self {
self.messages.get_or_insert_with(Vec::new).push(message);
self
}
pub fn messages(mut self, messages: Vec<WebSocketMessage>) -> Self {
self.messages = Some(messages);
self
}
pub fn close_connection(mut self, close: bool) -> Self {
self.close_connection = Some(close);
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 DnsRecord {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub record_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
}
impl DnsRecord {
pub fn new() -> Self {
Self::default()
}
pub fn a(name: impl Into<String>, ip: impl Into<String>) -> Self {
Self::new().name(name).record_type("A").value(ip)
}
pub fn aaaa(name: impl Into<String>, ip: impl Into<String>) -> Self {
Self::new().name(name).record_type("AAAA").value(ip)
}
pub fn cname(name: impl Into<String>, target: impl Into<String>) -> Self {
Self::new().name(name).record_type("CNAME").value(target)
}
pub fn txt(name: impl Into<String>, text: impl Into<String>) -> Self {
Self::new().name(name).record_type("TXT").value(text)
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn record_type(mut self, record_type: impl Into<String>) -> Self {
self.record_type = Some(record_type.into());
self
}
pub fn dns_class(mut self, dns_class: impl Into<String>) -> Self {
self.dns_class = Some(dns_class.into());
self
}
pub fn ttl(mut self, ttl: u32) -> Self {
self.ttl = Some(ttl);
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.value = Some(value.into());
self
}
pub fn priority(mut self, priority: u32) -> Self {
self.priority = Some(priority);
self
}
pub fn weight(mut self, weight: u32) -> Self {
self.weight = Some(weight);
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DnsResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub answer_records: Option<Vec<DnsRecord>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authority_records: Option<Vec<DnsRecord>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_records: Option<Vec<DnsRecord>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl DnsResponse {
pub fn new() -> Self {
Self::default()
}
pub fn answer_record(mut self, record: DnsRecord) -> Self {
self.answer_records
.get_or_insert_with(Vec::new)
.push(record);
self
}
pub fn answer_records(mut self, records: Vec<DnsRecord>) -> Self {
self.answer_records = Some(records);
self
}
pub fn authority_record(mut self, record: DnsRecord) -> Self {
self.authority_records
.get_or_insert_with(Vec::new)
.push(record);
self
}
pub fn additional_record(mut self, record: DnsRecord) -> Self {
self.additional_records
.get_or_insert_with(Vec::new)
.push(record);
self
}
pub fn response_code(mut self, code: impl Into<String>) -> Self {
self.response_code = Some(code.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 BinaryResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub binary_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl BinaryResponse {
pub fn new() -> Self {
Self::default()
}
pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
Self {
binary_data: Some(BASE64.encode(data.as_ref())),
delay: None,
}
}
pub fn from_base64(base64: impl Into<String>) -> Self {
Self {
binary_data: Some(base64.into()),
delay: None,
}
}
pub fn binary_data(mut self, data: impl AsRef<[u8]>) -> Self {
self.binary_data = Some(BASE64.encode(data.as_ref()));
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 GrpcStreamMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub json: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl GrpcStreamMessage {
pub fn json(json: impl Into<String>) -> Self {
Self {
json: Some(json.into()),
template_type: None,
delay: None,
}
}
pub fn template_type(mut self, template_type: impl Into<String>) -> Self {
self.template_type = Some(template_type.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 GrpcStreamResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub status_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<GrpcStreamMessage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_connection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
}
impl GrpcStreamResponse {
pub fn new() -> Self {
Self::default()
}
pub fn status_name(mut self, status_name: impl Into<String>) -> Self {
self.status_name = Some(status_name.into());
self
}
pub fn status_message(mut self, status_message: impl Into<String>) -> Self {
self.status_message = Some(status_message.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 message(mut self, message: GrpcStreamMessage) -> Self {
self.messages.get_or_insert_with(Vec::new).push(message);
self
}
pub fn messages(mut self, messages: Vec<GrpcStreamMessage>) -> Self {
self.messages = Some(messages);
self
}
pub fn close_connection(mut self, close: bool) -> Self {
self.close_connection = Some(close);
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 OpenApiExpectation {
pub spec_url_or_payload: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub operations_and_responses: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_path_prefix: Option<String>,
}
impl OpenApiExpectation {
pub fn new(spec_url_or_payload: impl Into<String>) -> Self {
Self {
spec_url_or_payload: spec_url_or_payload.into(),
operations_and_responses: None,
context_path_prefix: None,
}
}
pub fn operation(
mut self,
operation_id: impl Into<String>,
status_code: impl Into<String>,
) -> Self {
self.operations_and_responses
.get_or_insert_with(HashMap::new)
.insert(operation_id.into(), status_code.into());
self
}
pub fn operations_and_responses(mut self, map: HashMap<String, String>) -> Self {
self.operations_and_responses = Some(map);
self
}
pub fn context_path_prefix(mut self, prefix: impl Into<String>) -> Self {
self.context_path_prefix = Some(prefix.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>,
#[serde(default)]
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>,
#[serde(default)]
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, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VerificationTimes {
pub at_least: Option<u32>,
pub at_most: Option<u32>,
}
impl Serialize for VerificationTimes {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("VerificationTimes", 2)?;
state.serialize_field("atLeast", &self.at_least.map_or(-1_i64, i64::from))?;
state.serialize_field("atMost", &self.at_most.map_or(-1_i64, i64::from))?;
state.end()
}
}
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, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ResponseMode {
Sequential,
Random,
Weighted,
Switch,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CrossProtocolTrigger {
DnsQuery,
WebsocketConnect,
GrpcRequest,
HttpRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CrossProtocolScenario {
pub trigger: CrossProtocolTrigger,
#[serde(skip_serializing_if = "Option::is_none")]
pub match_pattern: Option<String>,
pub scenario_name: String,
pub target_state: String,
}
impl CrossProtocolScenario {
pub fn new(
trigger: CrossProtocolTrigger,
scenario_name: impl Into<String>,
target_state: impl Into<String>,
) -> Self {
Self {
trigger,
match_pattern: None,
scenario_name: scenario_name.into(),
target_state: target_state.into(),
}
}
pub fn match_pattern(mut self, pattern: impl Into<String>) -> Self {
self.match_pattern = Some(pattern.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub suppress_content_length_header: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_length_header_override: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suppress_connection_header: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chunk_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chunk_delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keep_alive_override: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_socket: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_socket_delay: Option<Delay>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl ConnectionOptions {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RecoverAfter {
#[serde(skip_serializing_if = "Option::is_none")]
pub fail_times: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fail_response: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub idempotency_header: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl RecoverAfter {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RateLimit {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window_millis: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub burst: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refill_per_second: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_status: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_after: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl RateLimit {
pub fn new() -> Self {
Self::default()
}
pub fn fixed_window(limit: i64, window_millis: i64) -> Self {
Self {
algorithm: Some("fixed_window".to_string()),
limit: Some(limit),
window_millis: Some(window_millis),
..Default::default()
}
}
pub fn token_bucket(burst: i64, refill_per_second: f64) -> Self {
Self {
algorithm: Some("token_bucket".to_string()),
burst: Some(burst),
refill_per_second: Some(refill_per_second),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpForwardWithFallback {
pub http_forward: HttpForward,
pub fallback_response: HttpResponse,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_on_status_codes: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_on_timeout: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl HttpForwardWithFallback {
pub fn new(http_forward: HttpForward, fallback_response: HttpResponse) -> Self {
Self {
http_forward,
fallback_response,
fallback_on_status_codes: None,
fallback_on_timeout: None,
delay: None,
primary: None,
extra: Extra::new(),
}
}
pub fn fallback_on_status_codes(mut self, codes: Vec<i32>) -> Self {
self.fallback_on_status_codes = Some(codes);
self
}
pub fn fallback_on_timeout(mut self, fallback: bool) -> Self {
self.fallback_on_timeout = Some(fallback);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpForwardValidateAction {
pub spec_url_or_payload: String,
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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validate_request: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validate_response: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validation_mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl HttpForwardValidateAction {
pub fn new(spec_url_or_payload: impl Into<String>, host: impl Into<String>) -> Self {
Self {
spec_url_or_payload: spec_url_or_payload.into(),
host: host.into(),
port: None,
scheme: None,
validate_request: None,
validate_response: None,
validation_mode: None,
delay: None,
primary: None,
extra: Extra::new(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpOverrideForwardedRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_override: Option<HttpRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_modifier: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_override: Option<HttpResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_modifier: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_template: Option<HttpTemplate>,
#[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 primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl HttpOverrideForwardedRequest {
pub fn new() -> Self {
Self::default()
}
pub fn request_override(mut self, request: HttpRequest) -> Self {
self.request_override = Some(request);
self
}
pub fn response_override(mut self, response: HttpResponse) -> Self {
self.response_override = Some(response);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExpectationAction {
#[serde(skip_serializing_if = "Option::is_none")]
pub http_request: Option<HttpRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_class_callback: Option<HttpClassCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_object_callback: Option<HttpObjectCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocking: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_policy: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl ExpectationAction {
pub fn request(request: HttpRequest) -> Self {
Self {
http_request: Some(request),
..Default::default()
}
}
pub fn class_callback(callback: HttpClassCallback) -> Self {
Self {
http_class_callback: Some(callback),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CaptureRule {
pub source: String,
pub expression: String,
pub into: String,
#[serde(flatten, default)]
pub extra: Extra,
}
impl CaptureRule {
pub fn new(
source: impl Into<String>,
expression: impl Into<String>,
into: impl Into<String>,
) -> Self {
Self {
source: source.into(),
expression: expression.into(),
into: into.into(),
extra: Extra::new(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExpectationStep {
#[serde(skip_serializing_if = "Option::is_none")]
pub http_request: Option<HttpRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_class_callback: Option<HttpClassCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_object_callback: Option<HttpObjectCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward: Option<HttpForward>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_override_forwarded_request: Option<HttpOverrideForwardedRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_response: Option<HttpResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_error: Option<HttpError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub responder: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocking: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_policy: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl ExpectationStep {
pub fn new() -> Self {
Self::default()
}
pub fn response(response: HttpResponse) -> Self {
Self {
http_response: Some(response),
responder: Some(true),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GrpcBidiMessage {
#[serde(skip_serializing_if = "Option::is_none")]
pub json: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl GrpcBidiMessage {
pub fn json(json: impl Into<String>) -> Self {
Self {
json: Some(json.into()),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GrpcBidiRule {
#[serde(skip_serializing_if = "Option::is_none")]
pub match_json: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub responses: Option<Vec<GrpcBidiMessage>>,
#[serde(flatten, default)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GrpcBidiResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub status_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub headers: Option<HashMap<String, Vec<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<GrpcBidiMessage>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rules: Option<Vec<GrpcBidiRule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub close_connection: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl GrpcBidiResponse {
pub fn new() -> Self {
Self::default()
}
pub fn message(mut self, message: GrpcBidiMessage) -> Self {
self.messages.get_or_insert_with(Vec::new).push(message);
self
}
pub fn rule(mut self, rule: GrpcBidiRule) -> Self {
self.rules.get_or_insert_with(Vec::new).push(rule);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LlmToolCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LlmUsage {
#[serde(skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_input_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_creation_tokens: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_tokens: Option<i64>,
#[serde(flatten, default)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LlmStreamingPhysics {
#[serde(skip_serializing_if = "Option::is_none")]
pub time_to_first_token: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tokens_per_second: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jitter: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subword_streaming: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LlmCompletion {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<LlmToolCall>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<LlmUsage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub streaming: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforce_output_schema: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub streaming_physics: Option<LlmStreamingPhysics>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl LlmCompletion {
pub fn text(text: impl Into<String>) -> Self {
Self {
text: Some(text.into()),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpLlmResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completion: Option<LlmCompletion>,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedding: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rerank: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub moderation: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_filter: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conversation_predicates: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaos: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub primary: Option<bool>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl HttpLlmResponse {
pub fn new() -> Self {
Self::default()
}
pub fn provider(mut self, provider: impl Into<String>) -> Self {
self.provider = Some(provider.into());
self
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn completion(mut self, completion: LlmCompletion) -> Self {
self.completion = Some(completion);
self
}
}
#[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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub percentage: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaos: Option<HttpChaosProfile>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate_limit: Option<RateLimit>,
#[serde(default, 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 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 http_response_class_callback: Option<HttpClassCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward_class_callback: Option<HttpClassCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_response_object_callback: Option<HttpObjectCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward_object_callback: Option<HttpObjectCallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_override_forwarded_request: Option<HttpOverrideForwardedRequest>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward_validate_action: Option<HttpForwardValidateAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_forward_with_fallback: Option<HttpForwardWithFallback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_sse_response: Option<HttpSseResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_llm_response: Option<HttpLlmResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_web_socket_response: Option<HttpWebSocketResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_response: Option<DnsResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub binary_response: Option<BinaryResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc_stream_response: Option<GrpcStreamResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc_bidi_response: Option<GrpcBidiResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub times: Option<Times>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_to_live: Option<TimeToLive>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scenario_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scenario_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_scenario_state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_responses: Option<Vec<HttpResponse>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_mode: Option<ResponseMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_weights: Option<Vec<i32>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub switch_after: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cross_protocol_scenarios: Option<Vec<CrossProtocolScenario>>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "one_or_many",
default
)]
pub before_actions: Option<Vec<ExpectationAction>>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "one_or_many",
default
)]
pub after_actions: Option<Vec<ExpectationAction>>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "one_or_many",
default
)]
pub capture: Option<Vec<CaptureRule>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespace: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub steps: Option<Vec<ExpectationStep>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<String>,
#[serde(flatten, default)]
pub extra: Extra,
}
impl Expectation {
pub fn new(request: HttpRequest) -> Self {
Self {
http_request: Some(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 respond_with_class_callback(mut self, callback_class: impl Into<String>) -> Self {
self.http_response_class_callback = Some(HttpClassCallback::new(callback_class));
self
}
pub fn respond_class_callback(mut self, callback: HttpClassCallback) -> Self {
self.http_response_class_callback = Some(callback);
self
}
pub fn forward_with_class_callback(mut self, callback_class: impl Into<String>) -> Self {
self.http_forward_class_callback = Some(HttpClassCallback::new(callback_class));
self
}
pub fn forward_class_callback(mut self, callback: HttpClassCallback) -> Self {
self.http_forward_class_callback = Some(callback);
self
}
pub fn respond_object_callback(mut self, callback: HttpObjectCallback) -> Self {
self.http_response_object_callback = Some(callback);
self
}
pub fn forward_object_callback(mut self, callback: HttpObjectCallback) -> Self {
self.http_forward_object_callback = Some(callback);
self
}
pub fn respond_sse(mut self, sse: HttpSseResponse) -> Self {
self.http_sse_response = Some(sse);
self
}
pub fn respond_web_socket(mut self, ws: HttpWebSocketResponse) -> Self {
self.http_web_socket_response = Some(ws);
self
}
pub fn respond_dns(mut self, dns: DnsResponse) -> Self {
self.dns_response = Some(dns);
self
}
pub fn respond_binary(mut self, binary: BinaryResponse) -> Self {
self.binary_response = Some(binary);
self
}
pub fn respond_grpc_stream(mut self, grpc: GrpcStreamResponse) -> Self {
self.grpc_stream_response = Some(grpc);
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
}
pub fn scenario_name(mut self, name: impl Into<String>) -> Self {
self.scenario_name = Some(name.into());
self
}
pub fn scenario_state(mut self, state: impl Into<String>) -> Self {
self.scenario_state = Some(state.into());
self
}
pub fn new_scenario_state(mut self, state: impl Into<String>) -> Self {
self.new_scenario_state = Some(state.into());
self
}
pub fn respond_with(mut self, response: HttpResponse) -> Self {
self.http_responses
.get_or_insert_with(Vec::new)
.push(response);
self
}
pub fn http_responses(mut self, responses: Vec<HttpResponse>) -> Self {
self.http_responses = Some(responses);
self
}
pub fn response_mode(mut self, mode: ResponseMode) -> Self {
self.response_mode = Some(mode);
self
}
pub fn response_weights(mut self, weights: Vec<i32>) -> Self {
self.response_weights = Some(weights);
self
}
pub fn switch_after(mut self, switch_after: i32) -> Self {
self.switch_after = Some(switch_after);
self
}
pub fn cross_protocol_scenario(mut self, scenario: CrossProtocolScenario) -> Self {
self.cross_protocol_scenarios
.get_or_insert_with(Vec::new)
.push(scenario);
self
}
pub fn cross_protocol_scenarios(mut self, scenarios: Vec<CrossProtocolScenario>) -> Self {
self.cross_protocol_scenarios = Some(scenarios);
self
}
pub fn percentage(mut self, percentage: i32) -> Self {
self.percentage = Some(percentage);
self
}
pub fn chaos(mut self, chaos: HttpChaosProfile) -> Self {
self.chaos = Some(chaos);
self
}
pub fn rate_limit(mut self, rate_limit: RateLimit) -> Self {
self.rate_limit = Some(rate_limit);
self
}
pub fn override_forwarded_request(
mut self,
override_request: HttpOverrideForwardedRequest,
) -> Self {
self.http_override_forwarded_request = Some(override_request);
self
}
pub fn forward_validate(mut self, action: HttpForwardValidateAction) -> Self {
self.http_forward_validate_action = Some(action);
self
}
pub fn forward_with_fallback(mut self, action: HttpForwardWithFallback) -> Self {
self.http_forward_with_fallback = Some(action);
self
}
pub fn respond_llm(mut self, llm: HttpLlmResponse) -> Self {
self.http_llm_response = Some(llm);
self
}
pub fn respond_grpc_bidi(mut self, grpc: GrpcBidiResponse) -> Self {
self.grpc_bidi_response = Some(grpc);
self
}
pub fn before_action(mut self, action: ExpectationAction) -> Self {
self.before_actions
.get_or_insert_with(Vec::new)
.push(action);
self
}
pub fn after_action(mut self, action: ExpectationAction) -> Self {
self.after_actions.get_or_insert_with(Vec::new).push(action);
self
}
pub fn capture_rule(mut self, rule: CaptureRule) -> Self {
self.capture.get_or_insert_with(Vec::new).push(rule);
self
}
pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
self.namespace = Some(namespace.into());
self
}
pub fn step(mut self, step: ExpectationStep) -> Self {
self.steps.get_or_insert_with(Vec::new).push(step);
self
}
pub fn steps(mut self, steps: Vec<ExpectationStep>) -> Self {
self.steps = Some(steps);
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, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ScenarioState {
pub scenario_name: String,
pub current_state: String,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct ScenarioList {
#[serde(default)]
pub scenarios: Vec<ScenarioState>,
}
#[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,
Java,
JavaScript,
Python,
Go,
CSharp,
Ruby,
Rust,
Php,
}
impl RetrieveFormat {
pub fn as_str(&self) -> &'static str {
match self {
RetrieveFormat::Json => "JSON",
RetrieveFormat::LogEntries => "LOG_ENTRIES",
RetrieveFormat::Java => "JAVA",
RetrieveFormat::JavaScript => "JAVASCRIPT",
RetrieveFormat::Python => "PYTHON",
RetrieveFormat::Go => "GO",
RetrieveFormat::CSharp => "CSHARP",
RetrieveFormat::Ruby => "RUBY",
RetrieveFormat::Rust => "RUST",
RetrieveFormat::Php => "PHP",
}
}
}
#[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",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PactVerification {
pub passed: bool,
pub report: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MockMode {
Simulate,
Spy,
Capture,
}
impl MockMode {
pub fn as_str(&self) -> &'static str {
match self {
MockMode::Simulate => "SIMULATE",
MockMode::Spy => "SPY",
MockMode::Capture => "CAPTURE",
}
}
pub fn proxy_unmatched_requests(&self) -> bool {
!matches!(self, MockMode::Simulate)
}
}
impl std::fmt::Display for MockMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for MockMode {
type Err = String;
fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
match value.trim().to_uppercase().as_str() {
"" => Err("mode is required (one of SIMULATE, SPY, CAPTURE)".to_string()),
"SIMULATE" => Ok(MockMode::Simulate),
"SPY" => Ok(MockMode::Spy),
"CAPTURE" => Ok(MockMode::Capture),
other => Err(format!(
"unknown mode '{other}' (expected one of SIMULATE, SPY, CAPTURE)"
)),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GrpcMethod {
pub name: String,
pub input_type: String,
pub output_type: String,
pub client_streaming: bool,
pub server_streaming: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GrpcService {
pub name: String,
pub methods: Vec<GrpcMethod>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SocketAddress {
pub host: String,
pub port: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub scheme: Option<String>,
}
impl SocketAddress {
pub fn new(host: impl Into<String>, port: u16) -> Self {
Self {
host: host.into(),
port,
scheme: None,
}
}
pub fn scheme(mut self, scheme: impl Into<String>) -> Self {
self.scheme = Some(scheme.into());
self
}
pub fn https(host: impl Into<String>, port: u16) -> Self {
Self::new(host, port).scheme("HTTPS")
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RampCurve {
Linear,
Quadratic,
Exponential,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadStageType {
Vu,
Rate,
Pause,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadStage {
#[serde(rename = "type")]
pub stage_type: LoadStageType,
pub duration_millis: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub curve: Option<RampCurve>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vus: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_vus: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_vus: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_rate: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_vus: Option<u32>,
}
impl LoadStage {
fn base(stage_type: LoadStageType, duration_millis: u64) -> Self {
Self {
stage_type,
duration_millis,
curve: None,
vus: None,
start_vus: None,
end_vus: None,
rate: None,
start_rate: None,
end_rate: None,
max_vus: None,
}
}
pub fn vu_hold(vus: u32, duration_millis: u64) -> Self {
let mut stage = Self::base(LoadStageType::Vu, duration_millis);
stage.vus = Some(vus);
stage
}
pub fn vu_ramp(start_vus: u32, end_vus: u32, duration_millis: u64, curve: RampCurve) -> Self {
let mut stage = Self::base(LoadStageType::Vu, duration_millis);
stage.start_vus = Some(start_vus);
stage.end_vus = Some(end_vus);
stage.curve = Some(curve);
stage
}
pub fn rate_hold(rate: f64, duration_millis: u64) -> Self {
let mut stage = Self::base(LoadStageType::Rate, duration_millis);
stage.rate = Some(rate);
stage
}
pub fn rate_ramp(
start_rate: f64,
end_rate: f64,
duration_millis: u64,
curve: RampCurve,
) -> Self {
let mut stage = Self::base(LoadStageType::Rate, duration_millis);
stage.start_rate = Some(start_rate);
stage.end_rate = Some(end_rate);
stage.curve = Some(curve);
stage
}
pub fn pause(duration_millis: u64) -> Self {
Self::base(LoadStageType::Pause, duration_millis)
}
pub fn max_vus(mut self, max_vus: u32) -> Self {
self.max_vus = Some(max_vus);
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadShapeType {
Spike,
Stairs,
RampHold,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadShapeMetric {
Vu,
Rate,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadShape {
#[serde(rename = "type")]
pub shape_type: LoadShapeType,
#[serde(skip_serializing_if = "Option::is_none")]
pub metric: Option<LoadShapeMetric>,
#[serde(skip_serializing_if = "Option::is_none")]
pub curve: Option<RampCurve>,
#[serde(skip_serializing_if = "Option::is_none")]
pub baseline: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub peak: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ramp_up_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hold_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ramp_down_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recovery_hold_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub step: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub steps: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub step_duration_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ramp_millis: Option<u64>,
}
impl LoadShape {
fn base(shape_type: LoadShapeType) -> Self {
Self {
shape_type,
metric: None,
curve: None,
baseline: None,
peak: None,
ramp_up_millis: None,
hold_millis: None,
ramp_down_millis: None,
recovery_hold_millis: None,
start: None,
step: None,
steps: None,
step_duration_millis: None,
target: None,
ramp_millis: None,
}
}
pub fn spike(
baseline: f64,
peak: f64,
ramp_up_millis: u64,
hold_millis: u64,
ramp_down_millis: u64,
) -> Self {
let mut shape = Self::base(LoadShapeType::Spike);
shape.baseline = Some(baseline);
shape.peak = Some(peak);
shape.ramp_up_millis = Some(ramp_up_millis);
shape.hold_millis = Some(hold_millis);
shape.ramp_down_millis = Some(ramp_down_millis);
shape
}
pub fn stairs(start: f64, step: f64, steps: u32, step_duration_millis: u64) -> Self {
let mut shape = Self::base(LoadShapeType::Stairs);
shape.start = Some(start);
shape.step = Some(step);
shape.steps = Some(steps);
shape.step_duration_millis = Some(step_duration_millis);
shape
}
pub fn ramp_hold(target: f64, ramp_millis: u64, hold_millis: u64) -> Self {
let mut shape = Self::base(LoadShapeType::RampHold);
shape.target = Some(target);
shape.ramp_millis = Some(ramp_millis);
shape.hold_millis = Some(hold_millis);
shape
}
pub fn metric(mut self, metric: LoadShapeMetric) -> Self {
self.metric = Some(metric);
self
}
pub fn curve(mut self, curve: RampCurve) -> Self {
self.curve = Some(curve);
self
}
pub fn recovery_hold_millis(mut self, recovery_hold_millis: u64) -> Self {
self.recovery_hold_millis = Some(recovery_hold_millis);
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadThresholdMetric {
LatencyP50,
LatencyP95,
LatencyP99,
LatencyP999,
ErrorRate,
ThroughputRps,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadComparator {
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadThreshold {
pub metric: LoadThresholdMetric,
pub comparator: LoadComparator,
pub threshold: f64,
}
impl LoadThreshold {
pub fn new(metric: LoadThresholdMetric, comparator: LoadComparator, threshold: f64) -> Self {
Self {
metric,
comparator,
threshold,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadPacingMode {
None,
ConstantPacing,
ConstantThroughput,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadPacing {
pub mode: LoadPacingMode,
pub value: f64,
}
impl LoadPacing {
pub fn new(mode: LoadPacingMode, value: f64) -> Self {
Self { mode, value }
}
pub fn constant_pacing(cycle_millis: f64) -> Self {
Self::new(LoadPacingMode::ConstantPacing, cycle_millis)
}
pub fn constant_throughput(iterations_per_second: f64) -> Self {
Self::new(LoadPacingMode::ConstantThroughput, iterations_per_second)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadFeederFormat {
Csv,
Json,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadFeederStrategy {
Circular,
Random,
Sequential,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadFeeder {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub rows: Vec<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<LoadFeederFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strategy: Option<LoadFeederStrategy>,
}
impl LoadFeeder {
pub fn rows(rows: Vec<HashMap<String, String>>) -> Self {
Self {
rows,
..Self::default()
}
}
pub fn data(data: impl Into<String>, format: LoadFeederFormat) -> Self {
Self {
data: Some(data.into()),
format: Some(format),
..Self::default()
}
}
pub fn strategy(mut self, strategy: LoadFeederStrategy) -> Self {
self.strategy = Some(strategy);
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadCaptureSource {
BodyJsonpath,
Header,
BodyRegex,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadCapture {
pub name: String,
pub source: LoadCaptureSource,
pub expression: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_value: Option<String>,
}
impl LoadCapture {
pub fn new(
name: impl Into<String>,
source: LoadCaptureSource,
expression: impl Into<String>,
) -> Self {
Self {
name: name.into(),
source,
expression: expression.into(),
default_value: None,
}
}
pub fn default_value(mut self, default_value: impl Into<String>) -> Self {
self.default_value = Some(default_value.into());
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LoadStepSelection {
Sequential,
Weighted,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadProfile {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub stages: Vec<LoadStage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shape: Option<LoadShape>,
}
impl LoadProfile {
pub fn of(stages: Vec<LoadStage>) -> Self {
Self {
stages,
shape: None,
}
}
pub fn shaped(shape: LoadShape) -> Self {
Self {
stages: Vec::new(),
shape: Some(shape),
}
}
pub fn constant(vus: u32, duration_millis: u64) -> Self {
Self::of(vec![LoadStage::vu_hold(vus, duration_millis)])
}
pub fn linear(start_vus: u32, end_vus: u32, duration_millis: u64) -> Self {
Self::of(vec![LoadStage::vu_ramp(
start_vus,
end_vus,
duration_millis,
RampCurve::Linear,
)])
}
pub fn add_stage(mut self, stage: LoadStage) -> Self {
self.stages.push(stage);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadStep {
pub request: HttpRequest,
#[serde(skip_serializing_if = "Option::is_none")]
pub think_time: Option<Delay>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub captures: Vec<LoadCapture>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight: Option<f64>,
}
impl LoadStep {
pub fn new(request: HttpRequest) -> Self {
Self {
request,
think_time: None,
captures: Vec::new(),
weight: None,
}
}
pub fn think_time(mut self, delay: Delay) -> Self {
self.think_time = Some(delay);
self
}
pub fn capture(mut self, capture: LoadCapture) -> Self {
self.captures.push(capture);
self
}
pub fn weight(mut self, weight: f64) -> Self {
self.weight = Some(weight);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct LoadScenario {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_requests: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_delay_millis: Option<u64>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub thresholds: Vec<LoadThreshold>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub abort_on_fail: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub abort_grace_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pacing: Option<LoadPacing>,
#[serde(skip_serializing_if = "Option::is_none")]
pub feeder: Option<LoadFeeder>,
#[serde(skip_serializing_if = "Option::is_none")]
pub step_selection: Option<LoadStepSelection>,
pub profile: LoadProfile,
pub steps: Vec<LoadStep>,
}
impl LoadScenario {
pub fn new(name: impl Into<String>, profile: LoadProfile, steps: Vec<LoadStep>) -> Self {
Self {
name: name.into(),
template_type: None,
max_requests: None,
start_delay_millis: None,
thresholds: Vec::new(),
abort_on_fail: false,
abort_grace_millis: None,
pacing: None,
feeder: None,
step_selection: None,
profile,
steps,
}
}
pub fn threshold(mut self, threshold: LoadThreshold) -> Self {
self.thresholds.push(threshold);
self
}
pub fn abort_on_fail(mut self, abort_on_fail: bool) -> Self {
self.abort_on_fail = abort_on_fail;
self
}
pub fn abort_grace_millis(mut self, abort_grace_millis: u64) -> Self {
self.abort_grace_millis = Some(abort_grace_millis);
self
}
pub fn pacing(mut self, pacing: LoadPacing) -> Self {
self.pacing = Some(pacing);
self
}
pub fn feeder(mut self, feeder: LoadFeeder) -> Self {
self.feeder = Some(feeder);
self
}
pub fn step_selection(mut self, step_selection: LoadStepSelection) -> Self {
self.step_selection = Some(step_selection);
self
}
pub fn template_type(mut self, template_type: impl Into<String>) -> Self {
self.template_type = Some(template_type.into());
self
}
pub fn max_requests(mut self, max_requests: u64) -> Self {
self.max_requests = Some(max_requests);
self
}
pub fn start_delay_millis(mut self, start_delay_millis: u64) -> Self {
self.start_delay_millis = Some(start_delay_millis);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SloObjective {
pub sli: String,
pub comparator: String,
pub threshold: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
}
impl SloObjective {
pub fn new(sli: impl Into<String>, comparator: impl Into<String>, threshold: f64) -> Self {
Self {
sli: sli.into(),
comparator: comparator.into(),
threshold,
scope: None,
}
}
pub fn scope(mut self, scope: impl Into<String>) -> Self {
self.scope = Some(scope.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SloWindow {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub window_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lookback_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_epoch_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to_epoch_millis: Option<u64>,
}
impl SloWindow {
pub fn lookback(millis: u64) -> Self {
Self {
window_type: Some("LOOKBACK".to_string()),
lookback_millis: Some(millis),
..Default::default()
}
}
pub fn explicit(from_epoch_millis: u64, to_epoch_millis: u64) -> Self {
Self {
window_type: Some("EXPLICIT".to_string()),
from_epoch_millis: Some(from_epoch_millis),
to_epoch_millis: Some(to_epoch_millis),
..Default::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SloCriteria {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<SloWindow>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum_sample_count: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub upstream_hosts: Option<Vec<String>>,
pub objectives: Vec<SloObjective>,
}
impl SloCriteria {
pub fn new(objectives: Vec<SloObjective>) -> Self {
Self {
name: None,
window: None,
minimum_sample_count: None,
upstream_hosts: None,
objectives,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn window(mut self, window: SloWindow) -> Self {
self.window = Some(window);
self
}
pub fn minimum_sample_count(mut self, count: u64) -> Self {
self.minimum_sample_count = Some(count);
self
}
pub fn upstream_hosts(mut self, hosts: Vec<String>) -> Self {
self.upstream_hosts = Some(hosts);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SloObjectiveResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub sli: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comparator: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub observed_value: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SloVerdict {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window_from_epoch_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window_to_epoch_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sample_count: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub objective_results: Vec<SloObjectiveResult>,
}
impl SloVerdict {
pub fn is_pass(&self) -> bool {
self.result.as_deref() == Some("PASS")
}
pub fn is_fail(&self) -> bool {
self.result.as_deref() == Some("FAIL")
}
pub fn is_inconclusive(&self) -> bool {
self.result.as_deref() == Some("INCONCLUSIVE")
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PreemptionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub drain_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_stream_id: Option<i64>,
}
impl PreemptionRequest {
pub fn new() -> Self {
Self::default()
}
pub fn mode(mut self, mode: impl Into<String>) -> Self {
self.mode = Some(mode.into());
self
}
pub fn drain_millis(mut self, millis: u64) -> Self {
self.drain_millis = Some(millis);
self
}
pub fn ttl_millis(mut self, millis: u64) -> Self {
self.ttl_millis = Some(millis);
self
}
pub fn last_stream_id(mut self, id: i64) -> Self {
self.last_stream_id = Some(id);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PreemptionStatus {
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub in_flight: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub drain_remaining_millis: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct HttpChaosProfile {
#[serde(skip_serializing_if = "Option::is_none")]
pub error_status: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_probability: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latency: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_drop: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub drop_connection_probability: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub succeed_first: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fail_request_count: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outage_after_millis: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outage_duration_millis: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncate_body_at_fraction: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub malformed_body: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_response_chunk_size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_response_chunk_delay: Option<Delay>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quota_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quota_limit: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quota_window_millis: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quota_error_status: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub degradation_ramp_millis: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql_errors: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql_error_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql_error_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub graphql_nullify_data: Option<bool>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
impl HttpChaosProfile {
pub fn new() -> Self {
Self::default()
}
pub fn error_status(mut self, status: u16) -> Self {
self.error_status = Some(status);
self
}
pub fn error_probability(mut self, probability: f64) -> Self {
self.error_probability = Some(probability);
self
}
pub fn latency(mut self, latency: Delay) -> Self {
self.latency = Some(latency);
self
}
pub fn connection_drop(mut self, drop: bool) -> Self {
self.connection_drop = Some(drop);
self
}
pub fn seed(mut self, seed: i64) -> Self {
self.seed = Some(seed);
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ChaosStage {
pub duration_millis: u64,
pub profiles: HashMap<String, HttpChaosProfile>,
}
impl ChaosStage {
pub fn new(duration_millis: u64) -> Self {
Self {
duration_millis,
profiles: HashMap::new(),
}
}
pub fn profile(mut self, host: impl Into<String>, profile: HttpChaosProfile) -> Self {
self.profiles.insert(host.into(), profile);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ChaosExperiment {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "loop", skip_serializing_if = "Option::is_none")]
pub loop_back: Option<bool>,
pub stages: Vec<ChaosStage>,
}
impl ChaosExperiment {
pub fn new(stages: Vec<ChaosStage>) -> Self {
Self {
name: None,
loop_back: None,
stages,
}
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn loop_back(mut self, loop_back: bool) -> Self {
self.loop_back = Some(loop_back);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grpc_services_deserialize_from_server_wire_shape() {
let wire = r#"[
{
"name": "helloworld.Greeter",
"methods": [
{
"name": "SayHello",
"inputType": "helloworld.HelloRequest",
"outputType": "helloworld.HelloReply",
"clientStreaming": false,
"serverStreaming": false
},
{
"name": "LotsOfReplies",
"inputType": "helloworld.HelloRequest",
"outputType": "helloworld.HelloReply",
"clientStreaming": false,
"serverStreaming": true
}
]
}
]"#;
let services: Vec<GrpcService> = serde_json::from_str(wire).unwrap();
assert_eq!(services.len(), 1);
let svc = &services[0];
assert_eq!(svc.name, "helloworld.Greeter");
assert_eq!(svc.methods.len(), 2);
let unary = &svc.methods[0];
assert_eq!(unary.name, "SayHello");
assert_eq!(unary.input_type, "helloworld.HelloRequest");
assert_eq!(unary.output_type, "helloworld.HelloReply");
assert!(!unary.client_streaming);
assert!(!unary.server_streaming);
let server_stream = &svc.methods[1];
assert_eq!(server_stream.name, "LotsOfReplies");
assert!(!server_stream.client_streaming);
assert!(server_stream.server_streaming);
}
#[test]
fn test_grpc_method_serializes_with_camel_case_keys() {
let method = GrpcMethod {
name: "BidiChat".into(),
input_type: "chat.Message".into(),
output_type: "chat.Message".into(),
client_streaming: true,
server_streaming: true,
};
let value = serde_json::to_value(&method).unwrap();
assert_eq!(value["name"], "BidiChat");
assert_eq!(value["inputType"], "chat.Message");
assert_eq!(value["outputType"], "chat.Message");
assert_eq!(value["clientStreaming"], true);
assert_eq!(value["serverStreaming"], true);
}
#[test]
fn test_grpc_services_empty_array() {
let services: Vec<GrpcService> = serde_json::from_str("[]").unwrap();
assert!(services.is_empty());
}
#[test]
fn test_grpc_service_round_trips() {
let original = GrpcService {
name: "helloworld.Greeter".into(),
methods: vec![GrpcMethod {
name: "SayHello".into(),
input_type: "helloworld.HelloRequest".into(),
output_type: "helloworld.HelloReply".into(),
client_streaming: false,
server_streaming: false,
}],
};
let json = serde_json::to_string(&original).unwrap();
let parsed: GrpcService = serde_json::from_str(&json).unwrap();
assert_eq!(original, parsed);
}
}