use super::*;
use rand::distributions::WeightedIndex;
use rand::prelude::*;
#[derive(Deserialize)]
pub struct StatusQuery {
body: Option<String>,
}
#[derive(Debug)]
struct WeightedChoice {
code: u16,
weight: f64,
}
fn parse_weighted_codes(codes_str: &str) -> Result<Vec<WeightedChoice>, String> {
let mut choices = Vec::new();
for choice in codes_str.split(',') {
let choice = choice.trim();
if choice.is_empty() {
continue;
}
let (code_str, weight) = if choice.contains(':') {
let parts: Vec<&str> = choice.split(':').collect();
if parts.len() != 2 {
return Err("Invalid format".to_string());
}
(
parts[0].trim(),
parts[1]
.trim()
.parse::<f64>()
.map_err(|_| "Invalid weight")?,
)
} else {
(choice, 1.0)
};
let code = code_str.parse::<u16>().map_err(|_| "Invalid status code")?;
if weight < 0.0 {
return Err("Weight cannot be negative".to_string());
}
choices.push(WeightedChoice { code, weight });
}
if choices.is_empty() {
return Err("No valid status codes found".to_string());
}
Ok(choices)
}
fn select_weighted_code(choices: &[WeightedChoice]) -> Result<u16, String> {
if choices.len() == 1 {
return Ok(choices[0].code);
}
let weights: Vec<f64> = choices.iter().map(|c| c.weight).collect();
if weights.iter().all(|&w| w == 0.0) {
return Err("All weights are zero".to_string());
}
let mut rng = thread_rng();
match WeightedIndex::new(&weights) {
Ok(dist) => {
let index = dist.sample(&mut rng);
Ok(choices[index].code)
}
Err(_) => {
let index = rng.gen_range(0..choices.len());
Ok(choices[index].code)
}
}
}
fn parse_accept_header(accept_header: Option<&actix_web::http::header::HeaderValue>) -> String {
if let Some(accept) = accept_header {
if let Ok(accept_str) = accept.to_str() {
let mut best_type = "text/plain";
let mut best_weight = 0.0;
for media_range in accept_str.split(',') {
let media_range = media_range.trim();
let parts: Vec<&str> = media_range.split(';').collect();
let mime_type = parts[0].trim();
let mut quality = 1.0;
for part in parts.iter().skip(1) {
if let Some(q_value) = part.trim().strip_prefix("q=") {
if let Ok(q) = q_value.parse::<f32>() {
quality = q;
}
}
}
if quality == 0.0 {
continue;
}
if mime_type == "*/*" || mime_type == "text/*" {
if quality > best_weight {
best_type = "text/plain";
best_weight = quality;
}
} else if quality > best_weight {
best_type = mime_type;
best_weight = quality;
}
}
best_type.to_string()
} else {
"text/plain".to_string()
}
} else {
"text/plain".to_string()
}
}
fn determine_response_content_type(req: &HttpRequest, has_body: bool) -> String {
if let Some(accept_header) = req.headers().get("accept") {
let accept_type = parse_accept_header(Some(accept_header));
if accept_type != "text/plain" {
return accept_type;
}
}
if has_body {
if let Some(content_type_header) = req.headers().get("content-type") {
if let Ok(content_type_str) = content_type_header.to_str() {
let mime_type = content_type_str
.split(';')
.next()
.unwrap_or(content_type_str)
.trim();
return mime_type.to_string();
}
}
}
"text/plain".to_string()
}
fn format_response_body(content: &str, content_type: &str) -> (String, String) {
match content_type {
"application/json" => {
match serde_json::from_str::<serde_json::Value>(content) {
Ok(_) => {
(content.to_string(), content_type.to_string())
}
Err(_) => {
let wrapped = json!({ "body": content });
(wrapped.to_string(), content_type.to_string())
}
}
}
_ => {
(content.to_string(), content_type.to_string())
}
}
}
pub async fn status_handler_get(
req: HttpRequest,
path: web::Path<String>,
query: web::Query<StatusQuery>,
) -> Result<HttpResponse> {
let codes_str = path.into_inner();
let choices = match parse_weighted_codes(&codes_str) {
Ok(choices) => choices,
Err(_) => {
return Ok(HttpResponse::BadRequest().json(json!({
"error": "Invalid status code"
})));
}
};
let chosen_code = match select_weighted_code(&choices) {
Ok(code) => code,
Err(_) => {
return Ok(HttpResponse::BadRequest().json(json!({
"error": "Invalid status code"
})));
}
};
let status = StatusCode::from_u16(chosen_code).unwrap_or(StatusCode::OK);
let should_be_empty = match chosen_code {
100..=199 => true,
204 => true,
304 => true,
_ => false,
};
if should_be_empty {
Ok(HttpResponse::build(status).finish())
} else {
if let Some(custom_body) = &query.body {
let content_type = determine_response_content_type(&req, false);
let (formatted_body, final_content_type) =
format_response_body(custom_body, &content_type);
Ok(HttpResponse::build(status)
.content_type(final_content_type)
.body(formatted_body))
} else {
Ok(HttpResponse::build(status).json(json!({
"status": chosen_code
})))
}
}
}
pub async fn status_handler(
req: HttpRequest,
path: web::Path<String>,
body: String,
query: web::Query<StatusQuery>,
) -> Result<HttpResponse> {
let codes_str = path.into_inner();
let choices = match parse_weighted_codes(&codes_str) {
Ok(choices) => choices,
Err(_) => {
return Ok(HttpResponse::BadRequest().json(json!({
"error": "Invalid status code"
})));
}
};
let chosen_code = match select_weighted_code(&choices) {
Ok(code) => code,
Err(_) => {
return Ok(HttpResponse::BadRequest().json(json!({
"error": "Invalid status code"
})));
}
};
let status = StatusCode::from_u16(chosen_code).unwrap_or(StatusCode::OK);
let should_be_empty = match chosen_code {
100..=199 => true,
204 => true,
304 => true,
_ => false,
};
if should_be_empty {
Ok(HttpResponse::build(status).finish())
} else {
let has_request_body = !body.trim().is_empty();
let custom_body = if has_request_body {
Some(body)
} else {
query.body.clone()
};
if let Some(custom_body) = custom_body {
let content_type = determine_response_content_type(&req, has_request_body);
let (formatted_body, final_content_type) =
format_response_body(&custom_body, &content_type);
Ok(HttpResponse::build(status)
.content_type(final_content_type)
.body(formatted_body))
} else {
Ok(HttpResponse::build(status).json(json!({
"status": chosen_code
})))
}
}
}
pub async fn status_options_handler(
_req: HttpRequest,
_path: web::Path<String>,
) -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.append_header((
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, TRACE, OPTIONS",
))
.append_header((
"Access-Control-Allow-Headers",
"Content-Type, Authorization, Accept, X-Requested-With",
))
.append_header(("Access-Control-Max-Age", "3600"))
.finish())
}