steel_decimal/
utils.rs

1// src/utils.rs
2use steel::rvals::SteelVal;
3use rust_decimal::prelude::*;
4use std::str::FromStr;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum ConversionError {
9    #[error("Invalid decimal format: {0}")]
10    InvalidDecimal(String),
11    #[error("Unsupported SteelVal type: {0:?}")]
12    UnsupportedType(SteelVal),
13    #[error("Type conversion failed: {0}")]
14    ConversionFailed(String),
15}
16
17/// Utility functions for converting between Rust types and Steel values
18pub struct TypeConverter;
19
20impl TypeConverter {
21    /// Convert a SteelVal to a Decimal
22    pub fn steel_val_to_decimal(val: &SteelVal) -> Result<Decimal, ConversionError> {
23        match val {
24            SteelVal::StringV(s) => {
25                Decimal::from_str(&s.to_string())
26                    .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", s, e)))
27            }
28            SteelVal::NumV(n) => {
29                Decimal::try_from(*n)
30                    .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", n, e)))
31            }
32            SteelVal::IntV(i) => {
33                Ok(Decimal::from(*i))
34            }
35            _ => Err(ConversionError::UnsupportedType(val.clone()))
36        }
37    }
38
39    /// Convert a Decimal to a SteelVal string
40    pub fn decimal_to_steel_val(decimal: Decimal) -> SteelVal {
41        SteelVal::StringV(decimal.to_string().into())
42    }
43
44    /// Convert multiple SteelVals to strings
45    pub fn steel_vals_to_strings(vals: Vec<SteelVal>) -> Result<Vec<String>, ConversionError> {
46        vals.into_iter()
47            .map(|val| Self::steel_val_to_string(val))
48            .collect()
49    }
50
51    /// Convert a single SteelVal to a string
52    pub fn steel_val_to_string(val: SteelVal) -> Result<String, ConversionError> {
53        match val {
54            SteelVal::StringV(s) => Ok(s.to_string()),
55            SteelVal::NumV(n) => Ok(n.to_string()),
56            SteelVal::IntV(i) => Ok(i.to_string()),
57            SteelVal::BoolV(b) => Ok(b.to_string()),
58            SteelVal::VectorV(v) => {
59                let string_values: Result<Vec<String>, _> = v.iter()
60                    .map(|item| Self::steel_val_to_string(item.clone()))
61                    .collect();
62                
63                match string_values {
64                    Ok(strings) => Ok(strings.join(",")),
65                    Err(e) => Err(e),
66                }
67            }
68            _ => Err(ConversionError::UnsupportedType(val))
69        }
70    }
71
72    /// Convert a string to a validated decimal string
73    pub fn validate_decimal_string(s: &str) -> Result<String, ConversionError> {
74        Decimal::from_str(s)
75            .map(|d| d.to_string())
76            .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", s, e)))
77    }
78
79    /// Convert common Rust types to decimal strings
80    pub fn f64_to_decimal_string(f: f64) -> Result<String, ConversionError> {
81        Decimal::try_from(f)
82            .map(|d| d.to_string())
83            .map_err(|e| ConversionError::ConversionFailed(format!("f64 to decimal: {}", e)))
84    }
85
86    pub fn i64_to_decimal_string(i: i64) -> String {
87        Decimal::from(i).to_string()
88    }
89
90    pub fn u64_to_decimal_string(u: u64) -> String {
91        Decimal::from(u).to_string()
92    }
93}
94
95/// Utility functions for script validation and analysis
96pub struct ScriptAnalyzer;
97
98impl ScriptAnalyzer {
99    /// Check if a string looks like a valid decimal number
100    pub fn is_decimal_like(s: &str) -> bool {
101        Decimal::from_str(s.trim()).is_ok()
102    }
103
104    /// Extract all string literals from a script (simple regex-based approach)
105    pub fn extract_string_literals(script: &str) -> Vec<String> {
106        let re = regex::Regex::new(r#""((?:\\.|[^"])*)""#).unwrap();
107        re.captures_iter(script)
108            .map(|cap| cap[1].to_string())
109            .collect()
110    }
111
112    /// Count function calls in a script
113    pub fn count_function_calls(script: &str, function_name: &str) -> usize {
114        let pattern = format!(r"\({}\s+", regex::escape(function_name));
115        let re = regex::Regex::new(&pattern).unwrap();
116        re.find_iter(script).count()
117    }
118
119    /// Check if script contains any decimal functions
120    pub fn contains_decimal_functions(script: &str) -> bool {
121        let decimal_functions = [
122            "decimal-add", "decimal-sub", "decimal-mul", "decimal-div",
123            "decimal-pow", "decimal-sqrt", "decimal-ln", "decimal-log10", "decimal-exp",
124            "decimal-sin", "decimal-cos", "decimal-tan",
125            "decimal-gt", "decimal-gte", "decimal-lt", "decimal-lte", "decimal-eq",
126            "decimal-abs", "decimal-round", "decimal-min", "decimal-max",
127        ];
128
129        decimal_functions.iter().any(|func| script.contains(func))
130    }
131}
132
133/// Utility functions for working with decimal precision
134pub struct DecimalPrecision;
135
136impl DecimalPrecision {
137    /// Set precision for a decimal string
138    pub fn set_precision(decimal_str: &str, precision: u32) -> Result<String, ConversionError> {
139        let decimal = Decimal::from_str(decimal_str)
140            .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", decimal_str, e)))?;
141        
142        Ok(decimal.round_dp(precision).to_string())
143    }
144
145    /// Get the number of decimal places in a decimal string
146    pub fn get_decimal_places(decimal_str: &str) -> Result<u32, ConversionError> {
147        let decimal = Decimal::from_str(decimal_str)
148            .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", decimal_str, e)))?;
149        
150        Ok(decimal.scale())
151    }
152
153    /// Normalize decimal string (remove trailing zeros)
154    pub fn normalize(decimal_str: &str) -> Result<String, ConversionError> {
155        let decimal = Decimal::from_str(decimal_str)
156            .map_err(|e| ConversionError::InvalidDecimal(format!("{}: {}", decimal_str, e)))?;
157        
158        Ok(decimal.normalize().to_string())
159    }
160}