Files
airun-pathfinder-crud-pricing/src/models.rs
James Bland ea8883d0da
All checks were successful
kinec.tech/airun-pathfinder-crud-pricing/pipeline/head This commit looks good
refactor: simplify crud-pricing to pure CRUD layer - remove AWS API clients
Removed AWS Pricing API and Cost Explorer clients to make crud-pricing
a pure DynamoDB CRUD layer. This fixes layer violation where CRUD was
making external AWS API calls.

Changes:
- Deleted src/aws_pricing.rs (AWS Pricing API client)
- Deleted src/cost_explorer.rs (Cost Explorer client)
- Removed PricingClient and StsClient from main.rs
- Removed QueryAwsApi and QueryCostExplorer operations
- Removed fetch_if_missing behavior (Get operation now only reads from cache)
- Removed aws-sdk-pricing, aws-sdk-costexplorer, aws-sdk-sts dependencies
- Removed pricing_api_access and sts_assume_role IAM policies

Result: Pure CRUD layer with only DynamoDB operations (Get, Put, ListCommon, IncrementAccess)
Reduced from ~1100 lines to ~600 lines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 04:44:44 -05:00

186 lines
4.9 KiB
Rust

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<String>,
#[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<u32>,
},
/// 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<String>,
/// 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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_cached: Option<String>,
}
/// 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<String>, // ["x86_64"], ["arm64"], etc.
/// OnDemand pricing
pub on_demand: OnDemandPricing,
/// Reserved pricing (if available)
#[serde(skip_serializing_if = "Option::is_none")]
pub reserved: Option<ReservedPricing>,
/// Spot pricing (if available)
#[serde(skip_serializing_if = "Option::is_none")]
pub spot: Option<SpotPricing>,
}
#[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<String, ReservedTerm>, // "1yr", "3yr"
pub convertible: HashMap<String, ReservedTerm>,
}
#[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<String>,
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 }),
}
}
}