use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Request payload for the Lambda function #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PricingRequest { pub operation: PricingOperation, } /// Operations supported by crud-pricing #[derive(Debug, Deserialize)] #[serde(tag = "type", rename_all = "camelCase")] pub enum PricingOperation { /// Get pricing from cache (optionally fetch from AWS if missing) Get { instance_type: String, region: String, pricing_type: PricingType, #[serde(default)] aws_account_id: Option, #[serde(default)] fetch_if_missing: bool, }, /// Put pricing data into cache Put { instance_type: String, region: String, pricing_type: PricingType, pricing_data: PricingData, }, /// List most commonly accessed instances ListCommon { #[serde(default = "default_limit")] limit: usize, #[serde(default)] min_access_count: Option, }, /// Increment access count for an instance IncrementAccess { instance_type: String, region: String, }, } fn default_limit() -> usize { 50 } /// Type of pricing data #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum PricingType { /// Public AWS list prices Retail, /// Account-specific prices (includes EDP/PPA automatically) AccountSpecific, } /// Complete pricing data for an instance #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PricingData { pub instance_type: String, pub region: String, pub pricing_type: PricingType, #[serde(skip_serializing_if = "Option::is_none")] pub aws_account_id: Option, /// EC2 instance pricing pub ec2_pricing: Ec2Pricing, /// Metadata pub source: String, // "aws-pricing-api" or "cost-explorer" pub last_updated: String, /// Cache/access tracking #[serde(default)] pub access_count: u32, #[serde(skip_serializing_if = "Option::is_none")] pub last_accessed: Option, #[serde(skip_serializing_if = "Option::is_none")] pub first_cached: Option, } /// EC2 instance pricing details #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Ec2Pricing { pub instance_family: String, // "m6g", "t3", etc. pub vcpus: i32, pub memory_gb: f64, pub architectures: Vec, // ["x86_64"], ["arm64"], etc. /// OnDemand pricing pub on_demand: OnDemandPricing, /// Reserved pricing (if available) #[serde(skip_serializing_if = "Option::is_none")] pub reserved: Option, /// Spot pricing (if available) #[serde(skip_serializing_if = "Option::is_none")] pub spot: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OnDemandPricing { pub hourly: f64, pub monthly: f64, // hourly * 730 } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReservedPricing { pub standard: HashMap, // "1yr", "3yr" pub convertible: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReservedTerm { pub all_upfront: ReservedOption, pub partial_upfront: ReservedOption, pub no_upfront: ReservedOption, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReservedOption { pub effective_hourly: f64, pub effective_monthly: f64, pub total_upfront: f64, pub monthly_payment: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SpotPricing { pub current_hourly: f64, pub avg_hourly: f64, // 30-day average pub max_hourly: f64, pub interruption_frequency: String, // "<5%", "5-10%", etc. pub savings_vs_on_demand_percent: i32, } /// Common instance returned by ListCommon operation #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct CommonInstance { pub instance_type: String, pub region: String, pub access_count: u32, pub last_accessed: Option, pub last_updated: String, } /// Response envelope #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PricingResponse { pub status_code: u16, pub body: serde_json::Value, } impl PricingResponse { pub fn success(data: impl Serialize) -> Self { Self { status_code: 200, body: serde_json::to_value(data).unwrap_or(serde_json::Value::Null), } } pub fn error(status_code: u16, message: &str) -> Self { Self { status_code, body: serde_json::json!({ "error": message }), } } }