Files
foxhunt/crates/config/src/schemas.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

231 lines
8.5 KiB
Rust

//! Configuration schemas and cloud storage configurations.
//!
//! This module defines configuration schemas for various cloud storage backends
//! and configuration versioning. Primarily focused on S3-compatible storage
//! for model artifacts and configuration management in the Foxhunt trading system.
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use uuid::Uuid;
/// Configuration schema metadata for versioning and tracking.
///
/// Provides versioning and audit trail information for configuration schemas.
///
/// Used to track configuration changes over time and maintain compatibility
/// across different versions of the trading system.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigSchema {
/// Unique identifier for this configuration schema
pub id: Uuid,
/// Semantic version string (e.g., "1.2.3")
pub version: String,
/// Timestamp when this schema was created
pub created_at: DateTime<Utc>,
/// Timestamp when this schema was last updated
pub updated_at: DateTime<Utc>,
}
/// Amazon S3 and S3-compatible storage configuration.
///
/// Configures access to S3 or S3-compatible storage services for storing
///
/// ML model artifacts, configuration backups, and other binary data.
///
/// Supports various authentication methods and connection options.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct S3Config {
/// S3 bucket name for storing model artifacts and data
pub bucket_name: String,
/// AWS region or S3-compatible service region
pub region: String,
/// AWS access key ID (optional, can use IAM roles or environment variables)
pub access_key_id: Option<String>,
/// AWS secret access key (optional, can use IAM roles or environment variables)
pub secret_access_key: Option<String>,
/// AWS session token for temporary credentials (optional)
pub session_token: Option<String>,
/// Custom S3-compatible endpoint URL (e.g., MinIO, DigitalOcean Spaces)
pub endpoint_url: Option<String>,
/// Force path-style URLs instead of virtual-hosted-style URLs
pub force_path_style: bool,
/// Request timeout duration for S3 operations
pub timeout: Duration,
/// Maximum number of retry attempts for failed requests
pub max_retry_attempts: u32,
/// Enable SSL/TLS for S3 connections
pub use_ssl: bool,
}
impl S3Config {
/// Validates the S3 configuration for correctness.
///
/// Performs validation checks on the S3 configuration to ensure all
/// required fields are present and have valid values before attempting
/// to establish connections to S3 services.
///
/// # Errors
///
/// Returns an error string if the configuration is invalid:
/// - Empty bucket name
///
/// - Empty region
/// - Invalid endpoint URL format
pub fn validate(&self) -> Result<(), String> {
if self.bucket_name.is_empty() {
return Err("S3 bucket name cannot be empty".to_owned());
}
if self.region.is_empty() {
return Err("S3 region cannot be empty".to_owned());
}
Ok(())
}
/// Create S3Config for testing purposes (generic testing)
#[cfg(test)]
pub fn default_for_testing(bucket: &str) -> Self {
Self {
bucket_name: bucket.to_owned(),
region: "us-east-1".to_owned(),
access_key_id: None,
secret_access_key: None,
session_token: None,
endpoint_url: None,
force_path_style: false,
timeout: Duration::from_secs(30),
max_retry_attempts: 3,
use_ssl: true,
}
}
/// Create S3Config for MinIO testing (real S3-compatible testing)
///
/// Available in test mode or when test-utils feature is enabled
pub fn for_minio_testing(bucket: &str) -> Self {
Self {
bucket_name: bucket.to_owned(),
region: "us-east-1".to_owned(),
access_key_id: Some("foxhunt_test".to_owned()),
secret_access_key: Some("foxhunt_test_password".to_owned()),
session_token: None,
endpoint_url: Some("http://localhost:9000".to_owned()),
force_path_style: true, // MinIO requires path-style
timeout: Duration::from_secs(30),
max_retry_attempts: 3,
use_ssl: false, // Local MinIO uses HTTP
}
}
}
/// Schema-level asset classification configuration for sector and type categorization.
///
/// Provides configuration-driven asset classification that replaces hardcoded
/// symbol-based classification logic. Supports flexible categorization rules
/// based on instrument properties rather than specific symbol names.
///
/// **Note**: This is a simpler schema-level config. For full asset classification
/// with volatility profiles and pattern rules, use `structures::AssetClassificationConfig`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetClassificationSchema {
/// Classification rules based on asset type patterns
pub asset_type_rules: HashMap<String, String>,
/// Default classifications for different asset categories
pub default_sectors: HashMap<String, String>,
/// Regex patterns for currency pair detection
pub currency_patterns: Vec<String>,
/// Regex patterns for cryptocurrency detection
pub crypto_patterns: Vec<String>,
}
impl AssetClassificationSchema {
/// Creates a new asset classification schema with default rules.
pub fn new() -> Self {
let mut asset_type_rules = HashMap::new();
asset_type_rules.insert("EQUITY".to_owned(), "Equity".to_owned());
asset_type_rules.insert("FOREX".to_owned(), "Currencies".to_owned());
asset_type_rules.insert("CRYPTO".to_owned(), "Cryptocurrency".to_owned());
asset_type_rules.insert("COMMODITY".to_owned(), "Commodities".to_owned());
asset_type_rules.insert("BOND".to_owned(), "Fixed Income".to_owned());
let mut default_sectors = HashMap::new();
default_sectors.insert("Equity".to_owned(), "Other".to_owned());
default_sectors.insert("Currencies".to_owned(), "Currencies".to_owned());
default_sectors.insert("Cryptocurrency".to_owned(), "Cryptocurrency".to_owned());
default_sectors.insert("Commodities".to_owned(), "Commodities".to_owned());
default_sectors.insert("Fixed Income".to_owned(), "Fixed Income".to_owned());
Self {
asset_type_rules,
default_sectors,
currency_patterns: vec![
r"^[A-Z]{3}[A-Z]{3}$".to_owned(), // USDEUR format
r".*USD.*".to_owned(),
r".*EUR.*".to_owned(),
r".*GBP.*".to_owned(),
r".*JPY.*".to_owned(),
],
crypto_patterns: vec![
r".*BTC.*".to_owned(),
r".*ETH.*".to_owned(),
r".*CRYPTO.*".to_owned(),
],
}
}
/// Classifies an instrument based on configuration rules.
pub fn classify_sector(&self, instrument_id: &str, asset_type: Option<&str>) -> String {
// First try to classify based on asset type if provided
if let Some(asset_type) = asset_type {
if let Some(sector) = self.asset_type_rules.get(asset_type) {
return sector.clone();
}
}
// Check for currency patterns
for pattern in &self.currency_patterns {
if let Ok(regex) = regex::Regex::new(pattern) {
if regex.is_match(instrument_id) {
return "Currencies".to_owned();
}
}
}
// Check for crypto patterns
for pattern in &self.crypto_patterns {
if let Ok(regex) = regex::Regex::new(pattern) {
if regex.is_match(instrument_id) {
return "Cryptocurrency".to_owned();
}
}
}
// Default classification
"Other".to_owned()
}
}
impl Default for AssetClassificationSchema {
fn default() -> Self {
Self::new()
}
}
impl Default for S3Config {
fn default() -> Self {
Self {
bucket_name: "foxhunt-models".to_owned(),
region: "us-east-1".to_owned(),
access_key_id: None,
secret_access_key: None,
session_token: None,
endpoint_url: None,
force_path_style: false,
timeout: Duration::from_secs(30),
max_retry_attempts: 3,
use_ssl: true,
}
}
}