use anyhow::Result;
use sha2::{Digest, Sha256};
use serde::Serialize;
pub struct PlanHasher;
impl PlanHasher {
pub fn hash_plan<T: Serialize>(plan: &T, seed: u64) -> Result<String> {
let plan_json = serde_json::to_string(plan)?;
let mut hasher = Sha256::new();
hasher.update(seed.to_be_bytes());
hasher.update(plan_json.as_bytes());
let result = hasher.finalize();
Ok(hex::encode(result))
}
pub fn hash_plan_short<T: Serialize>(plan: &T, seed: u64) -> Result<String> {
let full_hash = Self::hash_plan(plan, seed)?;
Ok(full_hash[..8].to_string())
}
}
pub mod random {
use rand::{rngs::StdRng, Rng, SeedableRng};
pub fn create_rng(seed: u64) -> StdRng {
StdRng::seed_from_u64(seed)
}
pub fn weighted_choice<'a, T>(items: &'a [T], weights: &[f64], rng: &mut StdRng) -> Option<&'a T> {
if items.is_empty() || items.len() != weights.len() {
return None;
}
let total_weight: f64 = weights.iter().sum();
if total_weight <= 0.0 {
return None;
}
let random_value = rng.gen::<f64>() * total_weight;
let mut cumulative_weight = 0.0;
for (i, &weight) in weights.iter().enumerate() {
cumulative_weight += weight;
if random_value <= cumulative_weight {
return Some(&items[i]);
}
}
items.last()
}
}
pub mod cost {
pub fn calculate_monthly_cost(base_cost: f64, rps: f64, multiplier: f64) -> f64 {
base_cost + (rps * multiplier * 24.0 * 30.0 / 1_000_000.0) }
pub fn apply_regional_pricing(base_cost: f64, region: &str) -> f64 {
match region {
"us-east-1" => base_cost,
"us-west-2" => base_cost * 1.1,
"eu-west-1" => base_cost * 1.2,
"ap-southeast-1" => base_cost * 1.3,
_ => base_cost,
}
}
}
pub mod slo {
pub fn composite_availability(availabilities: &[f64]) -> f64 {
availabilities.iter().product()
}
pub fn composite_p95_latency(latencies: &[f64]) -> f64 {
latencies.iter().sum()
}
pub fn parallel_p95_latency(latencies: &[f64]) -> f64 {
latencies.iter().fold(0.0, |acc, &x| acc.max(x))
}
}
pub mod config {
use std::path::Path;
pub fn file_exists_and_readable(path: &Path) -> bool {
path.exists() && path.is_file()
}
pub fn get_file_extension(path: &Path) -> Option<String> {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_lowercase())
}
pub fn is_supported_format(path: &Path) -> bool {
match get_file_extension(path) {
Some(ext) => matches!(ext.as_str(), "yaml" | "yml" | "json"),
None => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_plan_hash_deterministic() {
let plan = json!({
"language": "Rust",
"backend": "Actix Web"
});
let hash1 = PlanHasher::hash_plan(&plan, 42).unwrap();
let hash2 = PlanHasher::hash_plan(&plan, 42).unwrap();
assert_eq!(hash1, hash2);
}
#[test]
fn test_plan_hash_different_seeds() {
let plan = json!({
"language": "Rust",
"backend": "Actix Web"
});
let hash1 = PlanHasher::hash_plan(&plan, 42).unwrap();
let hash2 = PlanHasher::hash_plan(&plan, 43).unwrap();
assert_ne!(hash1, hash2);
}
#[test]
fn test_short_hash() {
let plan = json!({
"language": "Rust"
});
let short_hash = PlanHasher::hash_plan_short(&plan, 42).unwrap();
assert_eq!(short_hash.len(), 8);
}
#[test]
fn test_weighted_choice() {
let mut rng = random::create_rng(42);
let items = vec!["A", "B", "C"];
let weights = vec![0.1, 0.8, 0.1];
let choice = random::weighted_choice(&items, &weights, &mut rng);
assert!(choice.is_some());
}
#[test]
fn test_cost_calculation() {
let monthly_cost = cost::calculate_monthly_cost(10.0, 100.0, 0.001);
assert!(monthly_cost > 10.0);
}
#[test]
fn test_composite_availability() {
let availabilities = vec![0.999, 0.998, 0.999];
let composite = slo::composite_availability(&availabilities);
assert!(composite < 0.999);
assert!(composite > 0.995);
}
#[test]
fn test_file_extension() {
use std::path::PathBuf;
let yaml_path = PathBuf::from("test.yaml");
let json_path = PathBuf::from("test.json");
let invalid_path = PathBuf::from("test.txt");
assert!(config::is_supported_format(&yaml_path));
assert!(config::is_supported_format(&json_path));
assert!(!config::is_supported_format(&invalid_path));
}
}