use std::time::Duration;
use azure_core::http::{
headers::{HeaderName, HeaderValue, Headers},
StatusCode,
};
use super::FaultInjectionErrorType;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct CustomResponse {
status_code: StatusCode,
headers: Headers,
body: Vec<u8>,
}
impl CustomResponse {
pub fn status_code(&self) -> StatusCode {
self.status_code
}
pub fn headers(&self) -> &Headers {
&self.headers
}
pub fn body(&self) -> &[u8] {
&self.body
}
}
pub struct CustomResponseBuilder {
status_code: StatusCode,
headers: Headers,
body: Vec<u8>,
}
impl CustomResponseBuilder {
pub fn new(status_code: StatusCode) -> Self {
Self {
status_code,
headers: Headers::new(),
body: Vec::new(),
}
}
pub fn with_header(
mut self,
name: impl Into<HeaderName>,
value: impl Into<HeaderValue>,
) -> Self {
self.headers.insert(name, value);
self
}
pub fn with_sub_status(self, code: u32) -> Self {
self.with_header("x-ms-substatus", code.to_string())
}
pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.body = body.into();
self
}
pub fn build(self) -> CustomResponse {
CustomResponse {
status_code: self.status_code,
headers: self.headers,
body: self.body,
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct FaultInjectionResult {
error_type: Option<FaultInjectionErrorType>,
custom_response: Option<CustomResponse>,
delay: Option<Duration>,
probability: f32,
}
impl FaultInjectionResult {
pub fn error_type(&self) -> Option<FaultInjectionErrorType> {
self.error_type
}
pub fn custom_response(&self) -> Option<&CustomResponse> {
self.custom_response.as_ref()
}
pub fn delay(&self) -> Option<Duration> {
self.delay
}
pub fn probability(&self) -> f32 {
self.probability
}
}
pub struct FaultInjectionResultBuilder {
error_type: Option<FaultInjectionErrorType>,
custom_response: Option<CustomResponse>,
delay: Option<Duration>,
probability: f32,
}
impl FaultInjectionResultBuilder {
pub fn new() -> Self {
Self {
error_type: None,
custom_response: None,
delay: None,
probability: 1.0,
}
}
pub fn with_error(mut self, error_type: FaultInjectionErrorType) -> Self {
self.error_type = Some(error_type);
self
}
pub fn with_custom_response(mut self, response: CustomResponse) -> Self {
self.custom_response = Some(response);
self
}
pub fn with_delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
pub fn with_probability(mut self, probability: f32) -> Self {
self.probability = if probability.is_finite() {
probability.clamp(0.0, 1.0)
} else {
0.0
};
self
}
pub fn build(self) -> FaultInjectionResult {
FaultInjectionResult {
error_type: self.error_type,
custom_response: self.custom_response,
delay: self.delay,
probability: self.probability,
}
}
}
impl Default for FaultInjectionResultBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{CustomResponseBuilder, FaultInjectionResultBuilder};
use crate::fault_injection::FaultInjectionErrorType;
use azure_core::http::StatusCode;
use std::time::Duration;
#[test]
fn builder_default_values() {
let error = FaultInjectionResultBuilder::new()
.with_error(FaultInjectionErrorType::Timeout)
.build();
assert_eq!(
error.error_type().unwrap(),
FaultInjectionErrorType::Timeout
);
assert!(error.delay().is_none());
assert!((error.probability() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn builder_probability_clamped_above() {
let error = FaultInjectionResultBuilder::new()
.with_error(FaultInjectionErrorType::ServiceUnavailable)
.with_probability(1.5)
.build();
assert!((error.probability() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn builder_probability_clamped_below() {
let error = FaultInjectionResultBuilder::new()
.with_error(FaultInjectionErrorType::ServiceUnavailable)
.with_probability(-0.5)
.build();
assert!(error.probability().abs() < f32::EPSILON);
}
#[test]
fn builder_with_custom_response() {
let body = b"{\"test\": true}".to_vec();
let result = FaultInjectionResultBuilder::new()
.with_custom_response(
CustomResponseBuilder::new(StatusCode::Ok)
.with_body(body.clone())
.build(),
)
.build();
assert!(result.error_type().is_none());
let custom = result.custom_response().unwrap();
assert_eq!(custom.status_code(), StatusCode::Ok);
assert_eq!(custom.body(), body);
}
#[test]
fn builder_with_delay() {
let result = FaultInjectionResultBuilder::new()
.with_delay(Duration::from_millis(200))
.build();
assert_eq!(result.delay(), Some(Duration::from_millis(200)));
}
#[test]
fn builder_probability_nan_normalized_to_zero() {
let error = FaultInjectionResultBuilder::new()
.with_error(FaultInjectionErrorType::ServiceUnavailable)
.with_probability(f32::NAN)
.build();
assert!(error.probability().abs() < f32::EPSILON);
}
#[test]
fn custom_response_builder_with_sub_status() {
let response = CustomResponseBuilder::new(StatusCode::Forbidden)
.with_sub_status(3)
.with_body(b"forbidden")
.build();
assert_eq!(response.status_code(), StatusCode::Forbidden);
assert_eq!(response.body(), b"forbidden");
}
}