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>
604 lines
22 KiB
Rust
604 lines
22 KiB
Rust
//! Error types for the risk management system
|
|
// #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // COMMENTED: Crate-level allows applied
|
|
|
|
use thiserror::Error;
|
|
|
|
use common::error::CommonError;
|
|
use common::types::Price;
|
|
|
|
use crate::risk_types::RiskSeverity;
|
|
|
|
/// Comprehensive error types for the risk management system
|
|
///
|
|
/// This enum covers all possible error conditions that can occur during risk
|
|
/// management operations, from configuration issues to critical safety violations.
|
|
/// Each error variant includes context-specific information to aid in debugging
|
|
/// and incident response.
|
|
#[derive(Debug, Error)]
|
|
#[error(transparent)]
|
|
pub enum RiskError {
|
|
/// Configuration error occurred during system initialization or validation
|
|
#[error("Configuration error: {0}")]
|
|
Config(String),
|
|
/// Database operation failed (connection, query, transaction, etc.)
|
|
#[error("Database error: {0}")]
|
|
Database(String),
|
|
/// Position limit exceeded for a specific instrument
|
|
#[error("Position limit exceeded: {instrument} has {current} but limit is {limit}")]
|
|
PositionLimitExceeded {
|
|
/// The financial instrument that exceeded its limit
|
|
instrument: String,
|
|
/// Current position size
|
|
current: Price,
|
|
/// Maximum allowed position size
|
|
limit: Price,
|
|
},
|
|
/// Value at Risk limit exceeded, indicating portfolio risk is too high
|
|
#[error("VaR limit exceeded: {var} exceeds limit of {limit}")]
|
|
VarLimitExceeded {
|
|
/// Current `VaR` value
|
|
var: Price,
|
|
/// Maximum allowed `VaR`
|
|
limit: Price,
|
|
},
|
|
/// Drawdown limit exceeded, indicating excessive portfolio losses
|
|
#[error("Drawdown limit exceeded: {drawdown}% exceeds limit of {limit}%")]
|
|
DrawdownLimitExceeded {
|
|
/// Current drawdown percentage
|
|
drawdown: Price,
|
|
/// Maximum allowed drawdown percentage
|
|
limit: Price,
|
|
},
|
|
/// Daily loss limit exceeded, triggering risk controls
|
|
#[error("Daily loss limit exceeded: {loss} exceeds limit of {limit}")]
|
|
DailyLossLimitExceeded {
|
|
/// Current daily loss amount
|
|
loss: Price,
|
|
/// Maximum allowed daily loss
|
|
limit: Price,
|
|
},
|
|
/// Circuit breaker is active, preventing trading on an instrument
|
|
#[error("Circuit breaker active for {instrument}: {reason}")]
|
|
CircuitBreakerActive {
|
|
/// The instrument with an active circuit breaker
|
|
instrument: String,
|
|
/// Reason why the circuit breaker was triggered
|
|
reason: String,
|
|
},
|
|
/// Kill switch is active, immediately halting operations
|
|
#[error("Kill switch active for {scope:?}: {message}")]
|
|
KillSwitchActive {
|
|
/// Scope of the kill switch (global, strategy, symbol)
|
|
scope: crate::risk_types::KillSwitchScope,
|
|
/// Detailed message about why the kill switch was activated
|
|
message: String,
|
|
},
|
|
/// Market data is unavailable for required calculations
|
|
#[error("Market data unavailable for {instrument}")]
|
|
MarketDataUnavailable {
|
|
/// The instrument for which market data is missing
|
|
instrument: String,
|
|
},
|
|
/// Insufficient historical data for reliable risk calculations
|
|
#[error("Insufficient historical data: need {required} but have {available}")]
|
|
InsufficientHistoricalData {
|
|
/// Number of data points required
|
|
required: usize,
|
|
/// Number of data points available
|
|
available: usize,
|
|
},
|
|
/// Correlation calculation failed between instruments
|
|
#[error("Correlation calculation failed: {reason}")]
|
|
CorrelationCalculationFailed {
|
|
/// Detailed reason for the correlation calculation failure
|
|
reason: String,
|
|
},
|
|
/// Stress test scenario failed, indicating portfolio vulnerability
|
|
#[error("Stress test failed: {scenario}")]
|
|
StressTestFailed {
|
|
/// The stress test scenario that failed
|
|
scenario: String,
|
|
},
|
|
/// Performance metric violated its threshold
|
|
#[error("Performance violation: {metric} = {value}, threshold = {threshold}")]
|
|
PerformanceViolation {
|
|
/// The performance metric that was violated
|
|
metric: String,
|
|
/// Current value of the metric
|
|
value: Price,
|
|
/// Threshold value that was exceeded
|
|
threshold: Price,
|
|
},
|
|
/// Regulatory compliance rule was violated
|
|
#[error("Compliance violation: {rule}")]
|
|
ComplianceViolation {
|
|
/// The compliance rule that was violated
|
|
rule: String,
|
|
},
|
|
/// User authorization failed for the requested operation
|
|
#[error("Authorization failed: {reason}")]
|
|
AuthorizationFailed {
|
|
/// Reason why authorization failed
|
|
reason: String,
|
|
},
|
|
/// Order validation failed
|
|
#[error("Invalid order: {reason}")]
|
|
InvalidOrder {
|
|
/// Reason why the order is invalid
|
|
reason: String,
|
|
},
|
|
/// Required service is unavailable
|
|
#[error("Service unavailable: {service}")]
|
|
ServiceUnavailable {
|
|
/// Name of the unavailable service
|
|
service: String,
|
|
},
|
|
/// Operation timed out after specified duration
|
|
#[error("Operation timeout: {timeout_ms}ms")]
|
|
Timeout {
|
|
/// Timeout duration in milliseconds
|
|
timeout_ms: u64,
|
|
},
|
|
/// Data serialization/deserialization failed
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(String),
|
|
/// Network communication error occurred
|
|
#[error("Network error: {0}")]
|
|
Network(String),
|
|
/// Internal system error - should not occur in normal operation
|
|
#[error("Internal error: {0}")]
|
|
Internal(String),
|
|
/// Data validation failed for a specific field
|
|
#[error("Validation error: {field} - {message}")]
|
|
Validation {
|
|
/// The field that failed validation
|
|
field: String,
|
|
/// Detailed validation error message
|
|
message: String,
|
|
},
|
|
/// System resource was exhausted (memory, connections, etc.)
|
|
#[error("Resource exhausted: {resource}")]
|
|
ResourceExhausted {
|
|
/// The resource that was exhausted
|
|
resource: String,
|
|
},
|
|
/// Mathematical calculation failed
|
|
#[error("Calculation error: {operation} failed - {reason}")]
|
|
Calculation {
|
|
/// The calculation operation that failed
|
|
operation: String,
|
|
/// Reason for the calculation failure
|
|
reason: String,
|
|
},
|
|
/// Rate limit exceeded, operation must wait
|
|
#[error("Rate limited: {remaining_ms}ms until reset")]
|
|
RateLimited {
|
|
/// Milliseconds remaining until rate limit resets
|
|
remaining_ms: u64,
|
|
},
|
|
/// Order side (buy/sell) is invalid
|
|
#[error("Invalid order side: {side}")]
|
|
InvalidOrderSide {
|
|
/// The invalid order side value
|
|
side: String,
|
|
},
|
|
/// Order type is invalid or not supported
|
|
#[error("Invalid order type: {order_type}")]
|
|
InvalidOrderType {
|
|
/// The invalid order type value
|
|
order_type: String,
|
|
},
|
|
/// Order quantity is invalid (negative, zero, too large, etc.)
|
|
#[error("Invalid quantity: {quantity}")]
|
|
InvalidQuantity {
|
|
/// The invalid quantity value
|
|
quantity: String,
|
|
},
|
|
/// Order price is invalid (negative, zero, outside valid range, etc.)
|
|
#[error("Invalid price: {price}")]
|
|
InvalidPrice {
|
|
/// The invalid price value
|
|
price: String,
|
|
},
|
|
/// Generic connection error occurred
|
|
#[error("Connection error: {message}")]
|
|
ConnectionError {
|
|
/// Detailed connection error message
|
|
message: String,
|
|
},
|
|
/// Configuration-related error occurred
|
|
#[error("Configuration error: {message}")]
|
|
Configuration {
|
|
/// Detailed configuration error message
|
|
message: String,
|
|
},
|
|
/// Generic validation error occurred
|
|
#[error("Validation error: {message}")]
|
|
ValidationError {
|
|
/// Detailed validation error message
|
|
message: String,
|
|
},
|
|
/// Serialization/deserialization error occurred
|
|
#[error("Serialization error: {message}")]
|
|
SerializationError {
|
|
/// Detailed serialization error message
|
|
message: String,
|
|
},
|
|
|
|
// NEW: Enhanced error types for hardened risk management
|
|
/// Type conversion failed between incompatible types
|
|
#[error("Type conversion error: {from_type} to {to_type} failed - {reason}")]
|
|
TypeConversion {
|
|
/// Source type being converted from
|
|
from_type: String,
|
|
/// Target type being converted to
|
|
to_type: String,
|
|
/// Reason for the conversion failure
|
|
reason: String,
|
|
},
|
|
|
|
/// Calculation error with simplified message
|
|
#[error("Calculation error: {0}")]
|
|
CalculationError(String),
|
|
|
|
/// Required configuration parameter is missing
|
|
#[error("Missing configuration: {config_key}")]
|
|
MissingConfiguration {
|
|
/// The configuration key that is missing
|
|
config_key: String,
|
|
},
|
|
|
|
/// Environment validation failed - missing requirements
|
|
#[error("Environment validation failed: {environment} requires {requirement}")]
|
|
EnvironmentValidation {
|
|
/// The environment being validated
|
|
environment: String,
|
|
/// The requirement that was not met
|
|
requirement: String,
|
|
},
|
|
|
|
/// Data integrity check failed
|
|
#[error("Data integrity error: {data_type} validation failed - {details}")]
|
|
DataIntegrity {
|
|
/// Type of data that failed integrity check
|
|
data_type: String,
|
|
/// Detailed information about the integrity failure
|
|
details: String,
|
|
},
|
|
|
|
/// Resource is temporarily unavailable
|
|
#[error("Resource unavailable: {resource} temporarily unavailable - {reason}")]
|
|
ResourceUnavailable {
|
|
/// The unavailable resource
|
|
resource: String,
|
|
/// Reason why the resource is unavailable
|
|
reason: String,
|
|
},
|
|
|
|
/// Safety limit exceeded - immediate intervention required
|
|
#[error("Safety limit exceeded: {limit_type} current={current} max={maximum}")]
|
|
SafetyLimitExceeded {
|
|
/// Type of safety limit that was exceeded
|
|
limit_type: String,
|
|
/// Current value that exceeded the limit
|
|
current: String,
|
|
/// Maximum allowed value
|
|
maximum: String,
|
|
},
|
|
|
|
/// Emergency stop triggered - all operations must halt immediately
|
|
#[error("Emergency stop triggered: {trigger} - immediate halt required")]
|
|
EmergencyStop {
|
|
/// The trigger that caused the emergency stop
|
|
trigger: String,
|
|
},
|
|
|
|
/// Production safety violation detected
|
|
#[error("Production safety violation: {violation} in {environment}")]
|
|
ProductionSafety {
|
|
/// Description of the safety violation
|
|
violation: String,
|
|
/// Environment where the violation occurred
|
|
environment: String,
|
|
},
|
|
|
|
/// Broker system error occurred
|
|
#[error("Broker connection error: {0}")]
|
|
BrokerError(String),
|
|
|
|
/// Broker connection failed
|
|
#[error("Broker connection error: {message}")]
|
|
BrokerConnection {
|
|
/// Detailed broker connection error message
|
|
message: String,
|
|
},
|
|
|
|
/// Connection to specific endpoint failed
|
|
#[error("Connection failed to {endpoint}: {reason}")]
|
|
Connection {
|
|
/// The endpoint that failed to connect
|
|
endpoint: String,
|
|
/// Reason for the connection failure
|
|
reason: String,
|
|
},
|
|
|
|
/// Market data system error occurred
|
|
#[error("Market data error: {0}")]
|
|
MarketDataError(String),
|
|
|
|
/// Required data is unavailable for calculations
|
|
#[error("Data unavailable: {resource} - {reason}")]
|
|
DataUnavailable {
|
|
/// The data resource that is unavailable
|
|
resource: String,
|
|
/// Reason why the data is unavailable
|
|
reason: String,
|
|
},
|
|
|
|
/// Arithmetic overflow detected during calculation
|
|
#[error("Arithmetic overflow: {operation} - {context}")]
|
|
ArithmeticOverflow {
|
|
/// The operation that overflowed
|
|
operation: String,
|
|
/// Context about the overflow
|
|
context: String,
|
|
},
|
|
}
|
|
|
|
/// Result type for risk management operations
|
|
pub type RiskResult<T> = Result<T, RiskError>;
|
|
|
|
/// Safe conversion helpers to eliminate `unwrap()` patterns
|
|
use num::ToPrimitive;
|
|
use rust_decimal::Decimal;
|
|
use std::fmt::Display;
|
|
|
|
/// Safely convert f64 to Price with context
|
|
pub fn f64_to_price_safe(value: f64, context: &str) -> RiskResult<Price> {
|
|
Price::from_f64(value).map_err(|_| RiskError::TypeConversion {
|
|
from_type: "f64".to_owned(),
|
|
to_type: "Price".to_owned(),
|
|
reason: format!("{context}: invalid f64 value {value}"),
|
|
})
|
|
}
|
|
|
|
/// Safely convert f64 to Decimal with context
|
|
pub fn f64_to_decimal_safe(value: f64, context: &str) -> RiskResult<Decimal> {
|
|
Decimal::try_from(value).map_err(|_| RiskError::TypeConversion {
|
|
from_type: "f64".to_owned(),
|
|
to_type: "Decimal".to_owned(),
|
|
reason: format!("{context}: invalid f64 value {value}"),
|
|
})
|
|
}
|
|
|
|
/// Safely convert Price to Decimal with context
|
|
pub fn price_to_decimal_safe(price: Price, context: &str) -> RiskResult<Decimal> {
|
|
price.to_decimal().map_err(|_| RiskError::TypeConversion {
|
|
from_type: "Price".to_owned(),
|
|
to_type: "Decimal".to_owned(),
|
|
reason: format!("{context}: price conversion failed"),
|
|
})
|
|
}
|
|
|
|
/// Safely convert Decimal to f64 with context
|
|
pub fn decimal_to_f64_safe(decimal: Decimal, context: &str) -> RiskResult<f64> {
|
|
decimal.to_f64().ok_or_else(|| RiskError::TypeConversion {
|
|
from_type: "Decimal".to_owned(),
|
|
to_type: "f64".to_owned(),
|
|
reason: format!("{context}: decimal too large for f64"),
|
|
})
|
|
}
|
|
|
|
/// Safely parse environment variable with context
|
|
pub fn parse_env_var<T: std::str::FromStr>(var_name: &str, context: &str) -> RiskResult<T>
|
|
where
|
|
T::Err: Display,
|
|
{
|
|
std::env::var(var_name)
|
|
.map_err(|_| RiskError::MissingConfiguration {
|
|
config_key: var_name.to_owned(),
|
|
})?
|
|
.parse()
|
|
.map_err(|e| RiskError::EnvironmentValidation {
|
|
environment: var_name.to_owned(),
|
|
requirement: format!("{context}: {e}"),
|
|
})
|
|
}
|
|
|
|
/// Safe division with zero check
|
|
pub fn safe_divide(numerator: Decimal, denominator: Decimal, context: &str) -> RiskResult<Decimal> {
|
|
if denominator.is_zero() {
|
|
return Err(RiskError::Calculation {
|
|
operation: "division".to_owned(),
|
|
reason: format!("{context}: division by zero"),
|
|
});
|
|
}
|
|
Ok(numerator / denominator)
|
|
}
|
|
|
|
// ELIMINATED: Re-exports removed to force explicit imports
|
|
|
|
// Also support FoxhuntResult<T> for consistency with error-handling framework
|
|
// Removed core dependency - use core instead
|
|
|
|
impl RiskError {
|
|
/// Get the severity level of this error
|
|
#[must_use]
|
|
pub const fn severity(&self) -> RiskSeverity {
|
|
match self {
|
|
RiskError::KillSwitchActive { .. }
|
|
| RiskError::DailyLossLimitExceeded { .. }
|
|
| RiskError::DrawdownLimitExceeded { .. }
|
|
| RiskError::EmergencyStop { .. }
|
|
| RiskError::ProductionSafety { .. } => RiskSeverity::Critical,
|
|
|
|
RiskError::PositionLimitExceeded { .. }
|
|
| RiskError::VarLimitExceeded { .. }
|
|
| RiskError::CircuitBreakerActive { .. }
|
|
| RiskError::ComplianceViolation { .. } => RiskSeverity::High,
|
|
|
|
RiskError::PerformanceViolation { .. }
|
|
| RiskError::MarketDataUnavailable { .. }
|
|
| RiskError::AuthorizationFailed { .. } => RiskSeverity::Medium,
|
|
|
|
_ => RiskSeverity::Low,
|
|
}
|
|
}
|
|
|
|
/// Check if the error should trigger a kill switch
|
|
#[must_use]
|
|
pub const fn should_trigger_kill_switch(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
RiskError::DailyLossLimitExceeded { .. } | RiskError::DrawdownLimitExceeded { .. }
|
|
)
|
|
}
|
|
|
|
/// Check if the error should trigger a circuit breaker
|
|
#[must_use]
|
|
pub const fn should_trigger_circuit_breaker(&self) -> bool {
|
|
matches!(
|
|
self,
|
|
RiskError::PositionLimitExceeded { .. }
|
|
| RiskError::VarLimitExceeded { .. }
|
|
| RiskError::PerformanceViolation { .. }
|
|
)
|
|
}
|
|
|
|
/// Get error code for logging and monitoring
|
|
#[must_use]
|
|
pub const fn error_code(&self) -> &'static str {
|
|
match self {
|
|
RiskError::Config(_) => "CONFIG_ERROR",
|
|
RiskError::Database(_) => "DATABASE_ERROR",
|
|
RiskError::PositionLimitExceeded { .. } => "POSITION_LIMIT_EXCEEDED",
|
|
RiskError::VarLimitExceeded { .. } => "VAR_LIMIT_EXCEEDED",
|
|
RiskError::DrawdownLimitExceeded { .. } => "DRAWDOWN_LIMIT_EXCEEDED",
|
|
RiskError::DailyLossLimitExceeded { .. } => "DAILY_LOSS_LIMIT_EXCEEDED",
|
|
RiskError::CircuitBreakerActive { .. } => "CIRCUIT_BREAKER_ACTIVE",
|
|
RiskError::KillSwitchActive { .. } => "KILL_SWITCH_ACTIVE",
|
|
RiskError::MarketDataUnavailable { .. } => "MARKET_DATA_UNAVAILABLE",
|
|
RiskError::InsufficientHistoricalData { .. } => "INSUFFICIENT_HISTORICAL_DATA",
|
|
RiskError::CorrelationCalculationFailed { .. } => "CORRELATION_CALCULATION_FAILED",
|
|
RiskError::StressTestFailed { .. } => "STRESS_TEST_FAILED",
|
|
RiskError::PerformanceViolation { .. } => "PERFORMANCE_VIOLATION",
|
|
RiskError::ComplianceViolation { .. } => "COMPLIANCE_VIOLATION",
|
|
RiskError::AuthorizationFailed { .. } => "AUTHORIZATION_FAILED",
|
|
RiskError::InvalidOrder { .. } => "INVALID_ORDER",
|
|
RiskError::ServiceUnavailable { .. } => "SERVICE_UNAVAILABLE",
|
|
RiskError::Timeout { .. } => "TIMEOUT",
|
|
RiskError::Serialization(_) => "SERIALIZATION_ERROR",
|
|
RiskError::Network(_) => "NETWORK_ERROR",
|
|
RiskError::Internal(_) => "INTERNAL_ERROR",
|
|
RiskError::Validation { .. } => "VALIDATION_ERROR",
|
|
RiskError::ResourceExhausted { .. } => "RESOURCE_EXHAUSTED",
|
|
RiskError::Calculation { .. } => "CALCULATION_ERROR",
|
|
RiskError::RateLimited { .. } => "RATE_LIMITED",
|
|
RiskError::InvalidOrderSide { .. } => "INVALID_ORDER_SIDE",
|
|
RiskError::InvalidOrderType { .. } => "INVALID_ORDER_TYPE",
|
|
RiskError::InvalidQuantity { .. } => "INVALID_QUANTITY",
|
|
RiskError::InvalidPrice { .. } => "INVALID_PRICE",
|
|
RiskError::ConnectionError { .. } => "CONNECTION_ERROR",
|
|
RiskError::Configuration { .. } => "CONFIGURATION_ERROR",
|
|
RiskError::ValidationError { .. } => "VALIDATION_ERROR",
|
|
RiskError::SerializationError { .. } => "SERIALIZATION_ERROR",
|
|
|
|
// NEW: Enhanced error codes
|
|
RiskError::TypeConversion { .. } => "TYPE_CONVERSION_ERROR",
|
|
RiskError::CalculationError(_) => "CALCULATION_ERROR",
|
|
RiskError::MissingConfiguration { .. } => "MISSING_CONFIGURATION",
|
|
RiskError::EnvironmentValidation { .. } => "ENVIRONMENT_VALIDATION_ERROR",
|
|
RiskError::DataIntegrity { .. } => "DATA_INTEGRITY_ERROR",
|
|
RiskError::ResourceUnavailable { .. } => "RESOURCE_UNAVAILABLE",
|
|
RiskError::SafetyLimitExceeded { .. } => "SAFETY_LIMIT_EXCEEDED",
|
|
RiskError::EmergencyStop { .. } => "EMERGENCY_STOP",
|
|
RiskError::ProductionSafety { .. } => "PRODUCTION_SAFETY_VIOLATION",
|
|
RiskError::BrokerError(_) => "BROKER_ERROR",
|
|
RiskError::BrokerConnection { .. } => "BROKER_CONNECTION_ERROR",
|
|
RiskError::Connection { .. } => "CONNECTION_ERROR",
|
|
RiskError::MarketDataError(_) => "MARKET_DATA_ERROR",
|
|
RiskError::DataUnavailable { .. } => "DATA_UNAVAILABLE",
|
|
RiskError::ArithmeticOverflow { .. } => "ARITHMETIC_OVERFLOW",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert from tokio timeout error
|
|
impl From<tokio::time::error::Elapsed> for RiskError {
|
|
fn from(_: tokio::time::error::Elapsed) -> Self {
|
|
RiskError::Timeout { timeout_ms: 0 }
|
|
}
|
|
}
|
|
|
|
/// Convert from `anyhow::Error`
|
|
impl From<anyhow::Error> for RiskError {
|
|
fn from(err: anyhow::Error) -> Self {
|
|
RiskError::Internal(err.to_string())
|
|
}
|
|
}
|
|
|
|
/// Convert from `CommonError` to `RiskError`
|
|
impl From<CommonError> for RiskError {
|
|
fn from(err: CommonError) -> Self {
|
|
RiskError::Internal(format!("CommonError: {err}"))
|
|
}
|
|
}
|
|
|
|
/// Convert from `CommonTypeError`
|
|
impl From<common::types::CommonTypeError> for RiskError {
|
|
fn from(err: common::types::CommonTypeError) -> Self {
|
|
RiskError::Internal(format!("CommonTypeError: {err}"))
|
|
}
|
|
}
|
|
|
|
/// Convert from `serde_json::Error`
|
|
impl From<serde_json::Error> for RiskError {
|
|
fn from(err: serde_json::Error) -> Self {
|
|
RiskError::Serialization(err.to_string())
|
|
}
|
|
}
|
|
|
|
/// Convert from `std::num::ParseFloatError`
|
|
impl From<std::num::ParseFloatError> for RiskError {
|
|
fn from(err: std::num::ParseFloatError) -> Self {
|
|
RiskError::TypeConversion {
|
|
from_type: "string".to_owned(),
|
|
to_type: "f64".to_owned(),
|
|
reason: err.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert from `std::num::ParseIntError`
|
|
impl From<std::num::ParseIntError> for RiskError {
|
|
fn from(err: std::num::ParseIntError) -> Self {
|
|
RiskError::TypeConversion {
|
|
from_type: "string".to_owned(),
|
|
to_type: "integer".to_owned(),
|
|
reason: err.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert from `std::env::VarError`
|
|
impl From<std::env::VarError> for RiskError {
|
|
fn from(err: std::env::VarError) -> Self {
|
|
match err {
|
|
std::env::VarError::NotPresent => RiskError::MissingConfiguration {
|
|
config_key: "unknown".to_owned(),
|
|
},
|
|
std::env::VarError::NotUnicode(_) => RiskError::EnvironmentValidation {
|
|
environment: "unknown".to_owned(),
|
|
requirement: "valid unicode".to_owned(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert from `std::io::Error`
|
|
impl From<std::io::Error> for RiskError {
|
|
fn from(err: std::io::Error) -> Self {
|
|
RiskError::Network(format!("IO error: {err}"))
|
|
}
|
|
}
|