rust_rule_engine/engine/
engine.rs

1use crate::engine::{facts::Facts, knowledge_base::KnowledgeBase};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Value};
4use std::collections::HashMap;
5use std::time::{Duration, Instant};
6
7/// Type for custom function implementations
8pub type CustomFunction = Box<dyn Fn(&[Value], &Facts) -> Result<Value> + Send + Sync>;
9
10/// Configuration options for the rule engine
11#[derive(Debug, Clone)]
12pub struct EngineConfig {
13    /// Maximum number of execution cycles
14    pub max_cycles: usize,
15    /// Execution timeout
16    pub timeout: Option<Duration>,
17    /// Enable performance statistics collection
18    pub enable_stats: bool,
19    /// Enable debug mode with verbose logging
20    pub debug_mode: bool,
21}
22
23impl Default for EngineConfig {
24    fn default() -> Self {
25        Self {
26            max_cycles: 100,
27            timeout: Some(Duration::from_secs(30)),
28            enable_stats: true,
29            debug_mode: false,
30        }
31    }
32}
33
34/// Result of rule engine execution
35#[derive(Debug, Clone)]
36pub struct GruleExecutionResult {
37    /// Number of execution cycles
38    pub cycle_count: usize,
39    /// Number of rules evaluated
40    pub rules_evaluated: usize,
41    /// Number of rules that fired
42    pub rules_fired: usize,
43    /// Total execution time
44    pub execution_time: Duration,
45}
46
47/// Rust Rule Engine - High-performance rule execution engine
48pub struct RustRuleEngine {
49    knowledge_base: KnowledgeBase,
50    config: EngineConfig,
51    custom_functions: HashMap<String, CustomFunction>,
52}
53
54impl RustRuleEngine {
55    /// Create a new RustRuleEngine with default configuration
56    pub fn new(knowledge_base: KnowledgeBase) -> Self {
57        Self {
58            knowledge_base,
59            config: EngineConfig::default(),
60            custom_functions: HashMap::new(),
61        }
62    }
63
64    /// Create a new RustRuleEngine with custom configuration
65    pub fn with_config(knowledge_base: KnowledgeBase, config: EngineConfig) -> Self {
66        Self {
67            knowledge_base,
68            config,
69            custom_functions: HashMap::new(),
70        }
71    }
72
73    /// Register a custom function
74    pub fn register_function<F>(&mut self, name: &str, func: F)
75    where
76        F: Fn(&[Value], &Facts) -> Result<Value> + Send + Sync + 'static,
77    {
78        self.custom_functions
79            .insert(name.to_string(), Box::new(func));
80    }
81
82    /// Check if a custom function is registered
83    pub fn has_function(&self, name: &str) -> bool {
84        self.custom_functions.contains_key(name)
85    }
86
87    /// Get reference to the knowledge base
88    pub fn knowledge_base(&self) -> &KnowledgeBase {
89        &self.knowledge_base
90    }
91
92    /// Get mutable reference to the knowledge base
93    pub fn knowledge_base_mut(&mut self) -> &mut KnowledgeBase {
94        &mut self.knowledge_base
95    }
96
97    /// Execute all rules in the knowledge base against the given facts
98    pub fn execute(&self, facts: &Facts) -> Result<GruleExecutionResult> {
99        let start_time = Instant::now();
100        let mut cycle_count = 0;
101        let mut rules_evaluated = 0;
102        let mut rules_fired = 0;
103
104        if self.config.debug_mode {
105            println!(
106                "🚀 Starting rule execution with {} rules",
107                self.knowledge_base.get_rules().len()
108            );
109        }
110
111        for cycle in 0..self.config.max_cycles {
112            cycle_count = cycle + 1;
113            let mut any_rule_fired = false;
114
115            // Check for timeout
116            if let Some(timeout) = self.config.timeout {
117                if start_time.elapsed() > timeout {
118                    return Err(RuleEngineError::EvaluationError {
119                        message: "Execution timeout exceeded".to_string(),
120                    });
121                }
122            }
123
124            // Get rules sorted by salience (highest first)
125            let mut rules = self.knowledge_base.get_rules().clone();
126            rules.sort_by(|a, b| b.salience.cmp(&a.salience));
127
128            for rule in &rules {
129                if !rule.enabled {
130                    continue;
131                }
132
133                rules_evaluated += 1;
134
135                if self.config.debug_mode {
136                    println!("📝 Evaluating rule: {}", rule.name);
137                }
138
139                // Evaluate rule conditions
140                if self.evaluate_conditions(&rule.conditions, facts)? {
141                    if self.config.debug_mode {
142                        println!(
143                            "🔥 Rule '{}' fired (salience: {})",
144                            rule.name, rule.salience
145                        );
146                    }
147
148                    // Execute actions
149                    for action in &rule.actions {
150                        self.execute_action(action, facts)?;
151                    }
152
153                    rules_fired += 1;
154                    any_rule_fired = true;
155                }
156            }
157
158            // If no rules fired in this cycle, we're done
159            if !any_rule_fired {
160                break;
161            }
162        }
163
164        let execution_time = start_time.elapsed();
165
166        Ok(GruleExecutionResult {
167            cycle_count,
168            rules_evaluated,
169            rules_fired,
170            execution_time,
171        })
172    }
173
174    /// Evaluate conditions against facts
175    fn evaluate_conditions(
176        &self,
177        conditions: &crate::engine::rule::ConditionGroup,
178        facts: &Facts,
179    ) -> Result<bool> {
180        use crate::engine::rule::ConditionGroup;
181
182        match conditions {
183            ConditionGroup::Single(condition) => self.evaluate_single_condition(condition, facts),
184            ConditionGroup::Compound {
185                left,
186                operator,
187                right,
188            } => {
189                let left_result = self.evaluate_conditions(left, facts)?;
190                let right_result = self.evaluate_conditions(right, facts)?;
191
192                match operator {
193                    crate::types::LogicalOperator::And => Ok(left_result && right_result),
194                    crate::types::LogicalOperator::Or => Ok(left_result || right_result),
195                    crate::types::LogicalOperator::Not => Err(RuleEngineError::EvaluationError {
196                        message: "NOT operator should not appear in compound conditions"
197                            .to_string(),
198                    }),
199                }
200            }
201            ConditionGroup::Not(condition) => {
202                let result = self.evaluate_conditions(condition, facts)?;
203                Ok(!result)
204            }
205        }
206    }
207
208    /// Evaluate a single condition
209    fn evaluate_single_condition(
210        &self,
211        condition: &crate::engine::rule::Condition,
212        facts: &Facts,
213    ) -> Result<bool> {
214        // Get the field value from facts
215        let field_value = facts.get_nested(&condition.field);
216
217        if field_value.is_none() {
218            return Ok(false); // Field not found, condition fails
219        }
220
221        let field_value = field_value.unwrap();
222
223        // Compare using the operator
224        Ok(condition.operator.evaluate(&field_value, &condition.value))
225    }
226
227    /// Execute an action
228    fn execute_action(&self, action: &ActionType, facts: &Facts) -> Result<()> {
229        match action {
230            ActionType::Set { field, value } => {
231                facts.set_nested(field, value.clone())?;
232                if self.config.debug_mode {
233                    println!("  ✅ Set {field} = {value:?}");
234                }
235            }
236            ActionType::Log { message } => {
237                println!("📋 LOG: {}", message);
238            }
239            ActionType::Call { function, args } => {
240                let result = self.execute_function_call(function, args, facts)?;
241                if self.config.debug_mode {
242                    println!("  📞 Called {function}({args:?}) -> {result}");
243                }
244            }
245            ActionType::MethodCall {
246                object,
247                method,
248                args,
249            } => {
250                let result = self.execute_method_call(object, method, args, facts)?;
251                if self.config.debug_mode {
252                    println!("  🔧 Called {object}.{method}({args:?}) -> {result}");
253                }
254            }
255            ActionType::Update { object } => {
256                if self.config.debug_mode {
257                    println!("  🔄 Updated {object}");
258                }
259                // Update action is mainly for working memory management
260                // In this implementation, it's mostly a no-op since we update in place
261            }
262            ActionType::Custom {
263                action_type,
264                params,
265            } => {
266                if self.config.debug_mode {
267                    println!("  🎯 Custom action: {action_type} with params: {params:?}");
268                }
269            }
270        }
271        Ok(())
272    }
273
274    /// Execute function call
275    fn execute_function_call(
276        &self,
277        function: &str,
278        args: &[Value],
279        facts: &Facts,
280    ) -> Result<String> {
281        let function_lower = function.to_lowercase();
282
283        // Handle built-in utility functions
284        match function_lower.as_str() {
285            "log" | "print" | "println" => self.handle_log_function(args),
286            "update" | "refresh" => self.handle_update_function(args),
287            "now" | "timestamp" => self.handle_timestamp_function(),
288            "random" => self.handle_random_function(args),
289            "format" | "sprintf" => self.handle_format_function(args),
290            "length" | "size" | "count" => self.handle_length_function(args),
291            "sum" | "add" => self.handle_sum_function(args),
292            "max" | "maximum" => self.handle_max_function(args),
293            "min" | "minimum" => self.handle_min_function(args),
294            "avg" | "average" => self.handle_average_function(args),
295            "round" => self.handle_round_function(args),
296            "floor" => self.handle_floor_function(args),
297            "ceil" | "ceiling" => self.handle_ceil_function(args),
298            "abs" | "absolute" => self.handle_abs_function(args),
299            "contains" | "includes" => self.handle_contains_function(args),
300            "startswith" | "begins_with" => self.handle_starts_with_function(args),
301            "endswith" | "ends_with" => self.handle_ends_with_function(args),
302            "lowercase" | "tolower" => self.handle_lowercase_function(args),
303            "uppercase" | "toupper" => self.handle_uppercase_function(args),
304            "trim" | "strip" => self.handle_trim_function(args),
305            "split" => self.handle_split_function(args),
306            "join" => self.handle_join_function(args),
307            _ => {
308                // Try to call custom user-defined function
309                self.handle_custom_function(function, args, facts)
310            }
311        }
312    }
313
314    /// Handle logging functions (log, print, println)
315    fn handle_log_function(&self, args: &[Value]) -> Result<String> {
316        let message = if args.is_empty() {
317            "".to_string()
318        } else if args.len() == 1 {
319            args[0].to_string()
320        } else {
321            args.iter()
322                .map(|v| v.to_string())
323                .collect::<Vec<_>>()
324                .join(" ")
325        };
326
327        println!("📋 {}", message);
328        Ok(message)
329    }
330
331    /// Handle update/refresh functions
332    fn handle_update_function(&self, args: &[Value]) -> Result<String> {
333        if let Some(arg) = args.first() {
334            Ok(format!("Updated: {}", arg.to_string()))
335        } else {
336            Ok("Updated".to_string())
337        }
338    }
339
340    /// Handle timestamp function
341    fn handle_timestamp_function(&self) -> Result<String> {
342        use std::time::{SystemTime, UNIX_EPOCH};
343        let timestamp = SystemTime::now()
344            .duration_since(UNIX_EPOCH)
345            .map_err(|e| RuleEngineError::EvaluationError {
346                message: format!("Failed to get timestamp: {}", e),
347            })?
348            .as_secs();
349        Ok(timestamp.to_string())
350    }
351
352    /// Handle random function
353    fn handle_random_function(&self, args: &[Value]) -> Result<String> {
354        use std::collections::hash_map::DefaultHasher;
355        use std::hash::{Hash, Hasher};
356
357        // Simple pseudo-random based on current time (for deterministic behavior in tests)
358        let mut hasher = DefaultHasher::new();
359        std::time::SystemTime::now().hash(&mut hasher);
360        let random_value = hasher.finish();
361
362        if args.is_empty() {
363            Ok((random_value % 100).to_string()) // 0-99
364        } else if let Some(Value::Number(max)) = args.first() {
365            let max_val = *max as u64;
366            Ok((random_value % max_val).to_string())
367        } else {
368            Ok(random_value.to_string())
369        }
370    }
371
372    /// Handle format function (simple sprintf-like)
373    fn handle_format_function(&self, args: &[Value]) -> Result<String> {
374        if args.is_empty() {
375            return Ok("".to_string());
376        }
377
378        let template = args[0].to_string();
379        let values: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
380
381        // Simple placeholder replacement: {0}, {1}, etc.
382        let mut result = template;
383        for (i, value) in values.iter().enumerate() {
384            result = result.replace(&format!("{{{}}}", i), value);
385        }
386
387        Ok(result)
388    }
389
390    /// Handle length/size functions
391    fn handle_length_function(&self, args: &[Value]) -> Result<String> {
392        if let Some(arg) = args.first() {
393            match arg {
394                Value::String(s) => Ok(s.len().to_string()),
395                Value::Array(arr) => Ok(arr.len().to_string()),
396                Value::Object(obj) => Ok(obj.len().to_string()),
397                _ => Ok("1".to_string()), // Single value has length 1
398            }
399        } else {
400            Ok("0".to_string())
401        }
402    }
403
404    /// Handle sum function
405    fn handle_sum_function(&self, args: &[Value]) -> Result<String> {
406        let sum = args.iter().fold(0.0, |acc, val| match val {
407            Value::Number(n) => acc + n,
408            Value::Integer(i) => acc + (*i as f64),
409            _ => acc,
410        });
411        Ok(sum.to_string())
412    }
413
414    /// Handle max function
415    fn handle_max_function(&self, args: &[Value]) -> Result<String> {
416        let max = args.iter().fold(f64::NEG_INFINITY, |acc, val| match val {
417            Value::Number(n) => acc.max(*n),
418            Value::Integer(i) => acc.max(*i as f64),
419            _ => acc,
420        });
421        Ok(max.to_string())
422    }
423
424    /// Handle min function
425    fn handle_min_function(&self, args: &[Value]) -> Result<String> {
426        let min = args.iter().fold(f64::INFINITY, |acc, val| match val {
427            Value::Number(n) => acc.min(*n),
428            Value::Integer(i) => acc.min(*i as f64),
429            _ => acc,
430        });
431        Ok(min.to_string())
432    }
433
434    /// Handle average function
435    fn handle_average_function(&self, args: &[Value]) -> Result<String> {
436        if args.is_empty() {
437            return Ok("0".to_string());
438        }
439
440        let (sum, count) = args.iter().fold((0.0, 0), |(sum, count), val| match val {
441            Value::Number(n) => (sum + n, count + 1),
442            Value::Integer(i) => (sum + (*i as f64), count + 1),
443            _ => (sum, count),
444        });
445
446        if count > 0 {
447            Ok((sum / count as f64).to_string())
448        } else {
449            Ok("0".to_string())
450        }
451    }
452
453    /// Handle mathematical functions
454    fn handle_round_function(&self, args: &[Value]) -> Result<String> {
455        if let Some(Value::Number(n)) = args.first() {
456            Ok(n.round().to_string())
457        } else if let Some(Value::Integer(i)) = args.first() {
458            Ok(i.to_string())
459        } else {
460            Err(RuleEngineError::EvaluationError {
461                message: "round() requires a numeric argument".to_string(),
462            })
463        }
464    }
465
466    fn handle_floor_function(&self, args: &[Value]) -> Result<String> {
467        if let Some(Value::Number(n)) = args.first() {
468            Ok(n.floor().to_string())
469        } else if let Some(Value::Integer(i)) = args.first() {
470            Ok(i.to_string())
471        } else {
472            Err(RuleEngineError::EvaluationError {
473                message: "floor() requires a numeric argument".to_string(),
474            })
475        }
476    }
477
478    fn handle_ceil_function(&self, args: &[Value]) -> Result<String> {
479        if let Some(Value::Number(n)) = args.first() {
480            Ok(n.ceil().to_string())
481        } else if let Some(Value::Integer(i)) = args.first() {
482            Ok(i.to_string())
483        } else {
484            Err(RuleEngineError::EvaluationError {
485                message: "ceil() requires a numeric argument".to_string(),
486            })
487        }
488    }
489
490    fn handle_abs_function(&self, args: &[Value]) -> Result<String> {
491        if let Some(Value::Number(n)) = args.first() {
492            Ok(n.abs().to_string())
493        } else if let Some(Value::Integer(i)) = args.first() {
494            Ok(i.abs().to_string())
495        } else {
496            Err(RuleEngineError::EvaluationError {
497                message: "abs() requires a numeric argument".to_string(),
498            })
499        }
500    }
501
502    /// Handle string functions
503    fn handle_contains_function(&self, args: &[Value]) -> Result<String> {
504        if args.len() >= 2 {
505            let haystack = args[0].to_string();
506            let needle = args[1].to_string();
507            Ok(haystack.contains(&needle).to_string())
508        } else {
509            Err(RuleEngineError::EvaluationError {
510                message: "contains() requires 2 arguments".to_string(),
511            })
512        }
513    }
514
515    fn handle_starts_with_function(&self, args: &[Value]) -> Result<String> {
516        if args.len() >= 2 {
517            let text = args[0].to_string();
518            let prefix = args[1].to_string();
519            Ok(text.starts_with(&prefix).to_string())
520        } else {
521            Err(RuleEngineError::EvaluationError {
522                message: "startswith() requires 2 arguments".to_string(),
523            })
524        }
525    }
526
527    fn handle_ends_with_function(&self, args: &[Value]) -> Result<String> {
528        if args.len() >= 2 {
529            let text = args[0].to_string();
530            let suffix = args[1].to_string();
531            Ok(text.ends_with(&suffix).to_string())
532        } else {
533            Err(RuleEngineError::EvaluationError {
534                message: "endswith() requires 2 arguments".to_string(),
535            })
536        }
537    }
538
539    fn handle_lowercase_function(&self, args: &[Value]) -> Result<String> {
540        if let Some(arg) = args.first() {
541            Ok(arg.to_string().to_lowercase())
542        } else {
543            Err(RuleEngineError::EvaluationError {
544                message: "lowercase() requires 1 argument".to_string(),
545            })
546        }
547    }
548
549    fn handle_uppercase_function(&self, args: &[Value]) -> Result<String> {
550        if let Some(arg) = args.first() {
551            Ok(arg.to_string().to_uppercase())
552        } else {
553            Err(RuleEngineError::EvaluationError {
554                message: "uppercase() requires 1 argument".to_string(),
555            })
556        }
557    }
558
559    fn handle_trim_function(&self, args: &[Value]) -> Result<String> {
560        if let Some(arg) = args.first() {
561            Ok(arg.to_string().trim().to_string())
562        } else {
563            Err(RuleEngineError::EvaluationError {
564                message: "trim() requires 1 argument".to_string(),
565            })
566        }
567    }
568
569    fn handle_split_function(&self, args: &[Value]) -> Result<String> {
570        if args.len() >= 2 {
571            let text = args[0].to_string();
572            let delimiter = args[1].to_string();
573            let parts: Vec<String> = text.split(&delimiter).map(|s| s.to_string()).collect();
574            Ok(format!("{:?}", parts)) // Return as debug string for now
575        } else {
576            Err(RuleEngineError::EvaluationError {
577                message: "split() requires 2 arguments".to_string(),
578            })
579        }
580    }
581
582    fn handle_join_function(&self, args: &[Value]) -> Result<String> {
583        if args.len() >= 2 {
584            let delimiter = args[0].to_string();
585            let parts: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
586            Ok(parts.join(&delimiter))
587        } else {
588            Err(RuleEngineError::EvaluationError {
589                message: "join() requires at least 2 arguments".to_string(),
590            })
591        }
592    }
593
594    /// Handle custom user-defined functions
595    fn handle_custom_function(
596        &self,
597        function: &str,
598        args: &[Value],
599        facts: &Facts,
600    ) -> Result<String> {
601        // Check if we have a registered custom function
602        if let Some(custom_func) = self.custom_functions.get(function) {
603            if self.config.debug_mode {
604                println!("🎯 Calling registered function: {}({:?})", function, args);
605            }
606
607            match custom_func(args, facts) {
608                Ok(result) => Ok(result.to_string()),
609                Err(e) => Err(e),
610            }
611        } else {
612            // Function not found - return error or placeholder
613            if self.config.debug_mode {
614                println!("⚠️ Custom function '{}' not registered", function);
615            }
616
617            Err(RuleEngineError::EvaluationError {
618                message: format!("Function '{}' is not registered. Use engine.register_function() to add custom functions.", function),
619            })
620        }
621    }
622
623    /// Execute method call on object
624    fn execute_method_call(
625        &self,
626        object_name: &str,
627        method: &str,
628        args: &[Value],
629        facts: &Facts,
630    ) -> Result<String> {
631        // Get the object from facts
632        let Some(object_value) = facts.get(object_name) else {
633            return Err(RuleEngineError::EvaluationError {
634                message: format!("Object '{}' not found in facts", object_name),
635            });
636        };
637
638        let method_lower = method.to_lowercase();
639
640        // Handle setter methods (set + property name)
641        if method_lower.starts_with("set") && args.len() == 1 {
642            return self.handle_setter_method(object_name, method, &args[0], object_value, facts);
643        }
644
645        // Handle getter methods (get + property name)
646        if method_lower.starts_with("get") && args.is_empty() {
647            return self.handle_getter_method(object_name, method, &object_value);
648        }
649
650        // Handle built-in methods
651        match method_lower.as_str() {
652            "tostring" => Ok(object_value.to_string()),
653            "update" => {
654                facts.add_value(object_name, object_value)?;
655                Ok(format!("Updated {}", object_name))
656            }
657            "reset" => self.handle_reset_method(object_name, object_value, facts),
658            _ => self.handle_property_access_or_fallback(
659                object_name,
660                method,
661                args.len(),
662                &object_value,
663            ),
664        }
665    }
666
667    /// Handle setter method calls (setXxx)
668    fn handle_setter_method(
669        &self,
670        object_name: &str,
671        method: &str,
672        new_value: &Value,
673        mut object_value: Value,
674        facts: &Facts,
675    ) -> Result<String> {
676        let property_name = Self::extract_property_name_from_setter(method);
677
678        match object_value {
679            Value::Object(ref mut obj) => {
680                obj.insert(property_name.clone(), new_value.clone());
681                facts.add_value(object_name, object_value)?;
682                Ok(format!(
683                    "Set {} to {}",
684                    property_name,
685                    new_value.to_string()
686                ))
687            }
688            _ => Err(RuleEngineError::EvaluationError {
689                message: format!("Cannot call setter on non-object type: {}", object_name),
690            }),
691        }
692    }
693
694    /// Handle getter method calls (getXxx)
695    fn handle_getter_method(
696        &self,
697        object_name: &str,
698        method: &str,
699        object_value: &Value,
700    ) -> Result<String> {
701        let property_name = Self::extract_property_name_from_getter(method);
702
703        match object_value {
704            Value::Object(obj) => {
705                if let Some(value) = obj.get(&property_name) {
706                    Ok(value.to_string())
707                } else {
708                    Err(RuleEngineError::EvaluationError {
709                        message: format!(
710                            "Property '{}' not found on object '{}'",
711                            property_name, object_name
712                        ),
713                    })
714                }
715            }
716            _ => Err(RuleEngineError::EvaluationError {
717                message: format!("Cannot call getter on non-object type: {}", object_name),
718            }),
719        }
720    }
721
722    /// Handle reset method call
723    fn handle_reset_method(
724        &self,
725        object_name: &str,
726        mut object_value: Value,
727        facts: &Facts,
728    ) -> Result<String> {
729        match object_value {
730            Value::Object(ref mut obj) => {
731                obj.clear();
732                facts.add_value(object_name, object_value)?;
733                Ok(format!("Reset {}", object_name))
734            }
735            _ => Err(RuleEngineError::EvaluationError {
736                message: format!("Cannot reset non-object type: {}", object_name),
737            }),
738        }
739    }
740
741    /// Handle property access or fallback to generic method call
742    fn handle_property_access_or_fallback(
743        &self,
744        object_name: &str,
745        method: &str,
746        arg_count: usize,
747        object_value: &Value,
748    ) -> Result<String> {
749        if let Value::Object(obj) = object_value {
750            // Try exact property name match
751            if let Some(value) = obj.get(method) {
752                return Ok(value.to_string());
753            }
754
755            // Try capitalized property name
756            let capitalized_method = Self::capitalize_first_letter(method);
757            if let Some(value) = obj.get(&capitalized_method) {
758                return Ok(value.to_string());
759            }
760        }
761
762        // Fallback to generic response
763        Ok(format!(
764            "Called {}.{} with {} args",
765            object_name, method, arg_count
766        ))
767    }
768
769    /// Extract property name from setter method (setXxx -> Xxx)
770    fn extract_property_name_from_setter(method: &str) -> String {
771        let property_name = &method[3..]; // Remove "set" prefix
772        Self::capitalize_first_letter(property_name)
773    }
774
775    /// Extract property name from getter method (getXxx -> Xxx)
776    fn extract_property_name_from_getter(method: &str) -> String {
777        let property_name = &method[3..]; // Remove "get" prefix
778        Self::capitalize_first_letter(property_name)
779    }
780
781    /// Helper function to capitalize first letter of a string
782    fn capitalize_first_letter(s: &str) -> String {
783        if s.is_empty() {
784            return String::new();
785        }
786        let mut chars = s.chars();
787        match chars.next() {
788            None => String::new(),
789            Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
790        }
791    }
792}