Skip to main content

rust_rule_engine/engine/
rule.rs

1#![allow(deprecated)]
2#![allow(clippy::type_complexity)]
3
4use crate::types::{ActionType, LogicalOperator, Operator, Value};
5use chrono::{DateTime, Utc};
6use std::collections::HashMap;
7
8#[cfg(feature = "streaming")]
9use std::time::Duration;
10
11/// Window specification for stream patterns
12#[cfg(feature = "streaming")]
13#[derive(Debug, Clone, PartialEq)]
14pub struct StreamWindow {
15    /// Window duration
16    pub duration: Duration,
17    /// Window type (sliding, tumbling, etc.)
18    pub window_type: StreamWindowType,
19}
20
21/// Stream window types
22#[cfg(feature = "streaming")]
23#[derive(Debug, Clone, PartialEq)]
24pub enum StreamWindowType {
25    /// Sliding window - continuously moves forward
26    Sliding,
27    /// Tumbling window - non-overlapping fixed intervals
28    Tumbling,
29    /// Session window - groups events with idle timeout
30    Session { timeout: Duration },
31}
32
33/// Expression in a condition - can be a field reference or function call
34#[derive(Debug, Clone)]
35pub enum ConditionExpression {
36    /// Direct field reference (e.g., User.age)
37    Field(String),
38    /// Function call with arguments (e.g., aiSentiment(User.text))
39    FunctionCall {
40        /// Function name
41        name: String,
42        /// Function arguments (field names or literal values)
43        args: Vec<String>,
44    },
45    /// Test CE - arbitrary expression that evaluates to boolean (CLIPS feature)
46    /// Example: test(calculate_discount(Order.amount) > 10.0)
47    Test {
48        /// Function name for the test
49        name: String,
50        /// Function arguments
51        args: Vec<String>,
52    },
53    /// Multi-field operation (CLIPS-inspired)
54    /// Examples:
55    /// - Order.items $?all_items (Collect)
56    /// - Product.tags contains "value" (Contains)
57    /// - Order.items count > 0 (Count)
58    /// - Queue.tasks first (First)
59    /// - Queue.tasks last (Last)
60    /// - ShoppingCart.items empty (IsEmpty)
61    MultiField {
62        /// Field name (e.g., "Order.items")
63        field: String,
64        /// Multi-field operation type
65        operation: String, // "collect", "contains", "count", "first", "last", "empty", "not_empty"
66        /// Optional variable for binding (e.g., "$?all_items")
67        variable: Option<String>,
68    },
69}
70
71/// Represents a single condition in a rule
72#[derive(Debug, Clone)]
73pub struct Condition {
74    /// The expression to evaluate (field or function call)
75    pub expression: ConditionExpression,
76    /// The comparison operator to use
77    pub operator: Operator,
78    /// The value to compare against
79    pub value: Value,
80
81    // Keep field for backward compatibility
82    #[deprecated(note = "Use expression instead")]
83    #[doc(hidden)]
84    pub field: String,
85}
86
87impl Condition {
88    /// Create a new condition with a field reference
89    pub fn new(field: String, operator: Operator, value: Value) -> Self {
90        Self {
91            expression: ConditionExpression::Field(field.clone()),
92            operator,
93            value,
94            field, // Keep for backward compatibility
95        }
96    }
97
98    /// Create a new condition with a function call
99    pub fn with_function(
100        function_name: String,
101        args: Vec<String>,
102        operator: Operator,
103        value: Value,
104    ) -> Self {
105        Self {
106            expression: ConditionExpression::FunctionCall {
107                name: function_name.clone(),
108                args,
109            },
110            operator,
111            value,
112            field: function_name, // Use function name for backward compat
113        }
114    }
115
116    /// Create a new Test CE condition
117    /// The function must return a boolean value
118    pub fn with_test(function_name: String, args: Vec<String>) -> Self {
119        Self {
120            expression: ConditionExpression::Test {
121                name: function_name.clone(),
122                args,
123            },
124            operator: Operator::Equal,   // Not used for Test CE
125            value: Value::Boolean(true), // Not used for Test CE
126            field: format!("test({})", function_name), // For backward compat
127        }
128    }
129
130    /// Create multi-field collect condition
131    /// Example: Order.items $?all_items
132    pub fn with_multifield_collect(field: String, variable: String) -> Self {
133        Self {
134            expression: ConditionExpression::MultiField {
135                field: field.clone(),
136                operation: "collect".to_string(),
137                variable: Some(variable),
138            },
139            operator: Operator::Equal,   // Not used for MultiField
140            value: Value::Boolean(true), // Not used
141            field,                       // For backward compat
142        }
143    }
144
145    /// Create multi-field count condition
146    /// Example: Order.items count > 0
147    pub fn with_multifield_count(field: String, operator: Operator, value: Value) -> Self {
148        Self {
149            expression: ConditionExpression::MultiField {
150                field: field.clone(),
151                operation: "count".to_string(),
152                variable: None,
153            },
154            operator,
155            value,
156            field, // For backward compat
157        }
158    }
159
160    /// Create multi-field first condition
161    /// Example: Queue.tasks first $first_task
162    pub fn with_multifield_first(field: String, variable: Option<String>) -> Self {
163        Self {
164            expression: ConditionExpression::MultiField {
165                field: field.clone(),
166                operation: "first".to_string(),
167                variable,
168            },
169            operator: Operator::Equal,   // Not used
170            value: Value::Boolean(true), // Not used
171            field,                       // For backward compat
172        }
173    }
174
175    /// Create multi-field last condition
176    /// Example: Queue.tasks last $last_task
177    pub fn with_multifield_last(field: String, variable: Option<String>) -> Self {
178        Self {
179            expression: ConditionExpression::MultiField {
180                field: field.clone(),
181                operation: "last".to_string(),
182                variable,
183            },
184            operator: Operator::Equal,   // Not used
185            value: Value::Boolean(true), // Not used
186            field,                       // For backward compat
187        }
188    }
189
190    /// Create multi-field empty condition
191    /// Example: ShoppingCart.items empty
192    pub fn with_multifield_empty(field: String) -> Self {
193        Self {
194            expression: ConditionExpression::MultiField {
195                field: field.clone(),
196                operation: "empty".to_string(),
197                variable: None,
198            },
199            operator: Operator::Equal,   // Not used
200            value: Value::Boolean(true), // Not used
201            field,                       // For backward compat
202        }
203    }
204
205    /// Create multi-field not_empty condition
206    /// Example: ShoppingCart.items not_empty
207    pub fn with_multifield_not_empty(field: String) -> Self {
208        Self {
209            expression: ConditionExpression::MultiField {
210                field: field.clone(),
211                operation: "not_empty".to_string(),
212                variable: None,
213            },
214            operator: Operator::Equal,   // Not used
215            value: Value::Boolean(true), // Not used
216            field,                       // For backward compat
217        }
218    }
219
220    /// Evaluate this condition against the given facts
221    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
222        match &self.expression {
223            ConditionExpression::Field(field_name) => {
224                // Get field value, or treat as Null if not found
225                let field_value = get_nested_value(facts, field_name)
226                    .cloned()
227                    .unwrap_or(Value::Null);
228
229                self.operator.evaluate(&field_value, &self.value)
230            }
231            ConditionExpression::FunctionCall { .. }
232            | ConditionExpression::Test { .. }
233            | ConditionExpression::MultiField { .. } => {
234                // Function calls, Test CE, and MultiField need engine context
235                // Will be handled by evaluate_with_engine
236                false
237            }
238        }
239    }
240
241    /// Evaluate condition with access to engine's function registry
242    /// This is needed for function call evaluation
243    pub fn evaluate_with_engine(
244        &self,
245        facts: &HashMap<String, Value>,
246        function_registry: &HashMap<
247            String,
248            std::sync::Arc<
249                dyn Fn(Vec<Value>, &HashMap<String, Value>) -> crate::errors::Result<Value>
250                    + Send
251                    + Sync,
252            >,
253        >,
254    ) -> bool {
255        match &self.expression {
256            ConditionExpression::Field(field_name) => {
257                // Get field value, or treat as Null if not found
258                let field_value = get_nested_value(facts, field_name)
259                    .cloned()
260                    .unwrap_or(Value::Null);
261
262                self.operator.evaluate(&field_value, &self.value)
263            }
264            ConditionExpression::FunctionCall { name, args } => {
265                // Call the function with arguments
266                if let Some(function) = function_registry.get(name) {
267                    // Resolve arguments from facts
268                    let arg_values: Vec<Value> = args
269                        .iter()
270                        .map(|arg| {
271                            get_nested_value(facts, arg)
272                                .cloned()
273                                .unwrap_or(Value::String(arg.clone()))
274                        })
275                        .collect();
276
277                    // Call function
278                    if let Ok(result) = function(arg_values, facts) {
279                        // Compare function result with expected value
280                        return self.operator.evaluate(&result, &self.value);
281                    }
282                }
283                false
284            }
285            ConditionExpression::Test { name, args } => {
286                // Test CE: Call the function and expect boolean result
287                if let Some(function) = function_registry.get(name) {
288                    // Resolve arguments from facts
289                    let arg_values: Vec<Value> = args
290                        .iter()
291                        .map(|arg| {
292                            get_nested_value(facts, arg)
293                                .cloned()
294                                .unwrap_or(Value::String(arg.clone()))
295                        })
296                        .collect();
297
298                    // Call function
299                    if let Ok(result) = function(arg_values, facts) {
300                        // Test CE expects boolean result directly
301                        match result {
302                            Value::Boolean(b) => return b,
303                            Value::Integer(i) => return i != 0,
304                            Value::Number(f) => return f != 0.0,
305                            Value::String(s) => return !s.is_empty(),
306                            _ => return false,
307                        }
308                    }
309                }
310                false
311            }
312            ConditionExpression::MultiField {
313                field,
314                operation,
315                variable: _,
316            } => {
317                // MultiField operations for array/collection handling
318                if let Some(field_value) = get_nested_value(facts, field) {
319                    match operation.as_str() {
320                        "empty" => {
321                            // Check if array is empty
322                            matches!(field_value, Value::Array(arr) if arr.is_empty())
323                        }
324                        "not_empty" => {
325                            // Check if array is not empty
326                            matches!(field_value, Value::Array(arr) if !arr.is_empty())
327                        }
328                        "count" => {
329                            // Get count and compare with value
330                            if let Value::Array(arr) = field_value {
331                                let count = Value::Integer(arr.len() as i64);
332                                self.operator.evaluate(&count, &self.value)
333                            } else {
334                                false
335                            }
336                        }
337                        "first" => {
338                            // Get first element and compare with value
339                            if let Value::Array(arr) = field_value {
340                                if let Some(first) = arr.first() {
341                                    self.operator.evaluate(first, &self.value)
342                                } else {
343                                    false
344                                }
345                            } else {
346                                false
347                            }
348                        }
349                        "last" => {
350                            // Get last element and compare with value
351                            if let Value::Array(arr) = field_value {
352                                if let Some(last) = arr.last() {
353                                    self.operator.evaluate(last, &self.value)
354                                } else {
355                                    false
356                                }
357                            } else {
358                                false
359                            }
360                        }
361                        "contains" => {
362                            // Check if array contains the specified value
363                            if let Value::Array(arr) = field_value {
364                                arr.iter()
365                                    .any(|item| self.operator.evaluate(item, &self.value))
366                            } else {
367                                false
368                            }
369                        }
370                        "collect" => {
371                            // Collect operation: just check if array exists and has values
372                            // Variable binding happens in RETE engine context
373                            matches!(field_value, Value::Array(arr) if !arr.is_empty())
374                        }
375                        _ => {
376                            // Unknown operation
377                            false
378                        }
379                    }
380                } else {
381                    false
382                }
383            }
384        }
385    }
386}
387
388/// Group of conditions with logical operators
389#[derive(Debug, Clone)]
390pub enum ConditionGroup {
391    /// A single condition
392    Single(Condition),
393    /// A compound condition with two sub-conditions and a logical operator
394    Compound {
395        /// The left side condition
396        left: Box<ConditionGroup>,
397        /// The logical operator (AND, OR)
398        operator: LogicalOperator,
399        /// The right side condition
400        right: Box<ConditionGroup>,
401    },
402    /// A negated condition group
403    Not(Box<ConditionGroup>),
404    /// Pattern matching: check if at least one fact matches the condition
405    Exists(Box<ConditionGroup>),
406    /// Pattern matching: check if all facts of the target type match the condition
407    Forall(Box<ConditionGroup>),
408    /// Accumulate pattern: aggregate values from matching facts
409    /// Example: accumulate(Order($amount: amount, status == "completed"), sum($amount))
410    Accumulate {
411        /// Variable to bind the result to (e.g., "$total")
412        result_var: String,
413        /// Source pattern to match facts (e.g., "Order")
414        source_pattern: String,
415        /// Field to extract from matching facts (e.g., "$amount: amount")
416        extract_field: String,
417        /// Conditions on the source pattern
418        source_conditions: Vec<String>,
419        /// Accumulate function to apply (sum, avg, count, min, max)
420        function: String,
421        /// Variable passed to function (e.g., "$amount" in "sum($amount)")
422        function_arg: String,
423    },
424    /// Stream pattern: match events from a stream with optional time window
425    /// Example: login: LoginEvent from stream("logins") over window(10 min, sliding)
426    #[cfg(feature = "streaming")]
427    StreamPattern {
428        /// Variable to bind the event to (e.g., "login")
429        var_name: String,
430        /// Optional event type filter (e.g., "LoginEvent")
431        event_type: Option<String>,
432        /// Stream name to read from (e.g., "logins")
433        stream_name: String,
434        /// Optional window specification (duration and type)
435        window: Option<StreamWindow>,
436    },
437}
438
439impl ConditionGroup {
440    /// Create a single condition group
441    pub fn single(condition: Condition) -> Self {
442        ConditionGroup::Single(condition)
443    }
444
445    /// Create a compound condition using logical AND operator
446    pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
447        ConditionGroup::Compound {
448            left: Box::new(left),
449            operator: LogicalOperator::And,
450            right: Box::new(right),
451        }
452    }
453
454    /// Create a compound condition using logical OR operator
455    pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
456        ConditionGroup::Compound {
457            left: Box::new(left),
458            operator: LogicalOperator::Or,
459            right: Box::new(right),
460        }
461    }
462
463    /// Create a negated condition using logical NOT operator
464    #[allow(clippy::should_implement_trait)]
465    pub fn not(condition: ConditionGroup) -> Self {
466        ConditionGroup::Not(Box::new(condition))
467    }
468
469    /// Create an exists condition - checks if at least one fact matches
470    pub fn exists(condition: ConditionGroup) -> Self {
471        ConditionGroup::Exists(Box::new(condition))
472    }
473
474    /// Create a forall condition - checks if all facts of target type match
475    pub fn forall(condition: ConditionGroup) -> Self {
476        ConditionGroup::Forall(Box::new(condition))
477    }
478
479    /// Create an accumulate condition - aggregates values from matching facts
480    pub fn accumulate(
481        result_var: String,
482        source_pattern: String,
483        extract_field: String,
484        source_conditions: Vec<String>,
485        function: String,
486        function_arg: String,
487    ) -> Self {
488        ConditionGroup::Accumulate {
489            result_var,
490            source_pattern,
491            extract_field,
492            source_conditions,
493            function,
494            function_arg,
495        }
496    }
497
498    /// Create a stream pattern condition - matches events from a stream
499    #[cfg(feature = "streaming")]
500    pub fn stream_pattern(
501        var_name: String,
502        event_type: Option<String>,
503        stream_name: String,
504        window: Option<StreamWindow>,
505    ) -> Self {
506        ConditionGroup::StreamPattern {
507            var_name,
508            event_type,
509            stream_name,
510            window,
511        }
512    }
513
514    /// Evaluate this condition group against facts
515    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
516        match self {
517            ConditionGroup::Single(condition) => condition.evaluate(facts),
518            ConditionGroup::Compound {
519                left,
520                operator,
521                right,
522            } => {
523                let left_result = left.evaluate(facts);
524                let right_result = right.evaluate(facts);
525                match operator {
526                    LogicalOperator::And => left_result && right_result,
527                    LogicalOperator::Or => left_result || right_result,
528                    LogicalOperator::Not => !left_result, // For Not, we ignore right side
529                }
530            }
531            ConditionGroup::Not(condition) => !condition.evaluate(facts),
532            ConditionGroup::Exists(_)
533            | ConditionGroup::Forall(_)
534            | ConditionGroup::Accumulate { .. } => {
535                // Pattern matching and accumulate conditions need Facts struct, not HashMap
536                // For now, return false - these will be handled by the engine
537                false
538            }
539            #[cfg(feature = "streaming")]
540            ConditionGroup::StreamPattern { .. } => {
541                // Stream patterns need special handling in RETE engine with stream nodes
542                // For now, return false - these will be handled by the streaming engine
543                false
544            }
545        }
546    }
547
548    /// Evaluate this condition group against Facts (supports pattern matching)
549    pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
550        use crate::engine::pattern_matcher::PatternMatcher;
551
552        match self {
553            ConditionGroup::Single(condition) => {
554                let fact_map = facts.get_all_facts();
555                condition.evaluate(&fact_map)
556            }
557            ConditionGroup::Compound {
558                left,
559                operator,
560                right,
561            } => {
562                let left_result = left.evaluate_with_facts(facts);
563                let right_result = right.evaluate_with_facts(facts);
564                match operator {
565                    LogicalOperator::And => left_result && right_result,
566                    LogicalOperator::Or => left_result || right_result,
567                    LogicalOperator::Not => !left_result,
568                }
569            }
570            ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
571            ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
572            ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
573            ConditionGroup::Accumulate { .. } => {
574                // Accumulate conditions need special handling - they will be evaluated
575                // during the engine execution phase, not here
576                // For now, return true to allow the rule to continue evaluation
577                true
578            }
579            #[cfg(feature = "streaming")]
580            ConditionGroup::StreamPattern { .. } => {
581                // Stream patterns need special handling in RETE engine with stream nodes
582                // They will be evaluated by the streaming engine, not here
583                // For now, return true to allow the rule to continue evaluation
584                true
585            }
586        }
587    }
588}
589
590/// A rule with conditions and actions
591#[derive(Debug, Clone)]
592pub struct Rule {
593    /// The unique name of the rule
594    pub name: String,
595    /// Optional description of what the rule does
596    pub description: Option<String>,
597    /// Priority of the rule (higher values execute first)
598    pub salience: i32,
599    /// Whether the rule is enabled for execution
600    pub enabled: bool,
601    /// Prevents the rule from activating itself in the same cycle
602    pub no_loop: bool,
603    /// Prevents the rule from firing again until agenda group changes
604    pub lock_on_active: bool,
605    /// Agenda group this rule belongs to (for workflow control)
606    pub agenda_group: Option<String>,
607    /// Activation group - only one rule in group can fire
608    pub activation_group: Option<String>,
609    /// Rule becomes effective from this date
610    pub date_effective: Option<DateTime<Utc>>,
611    /// Rule expires after this date
612    pub date_expires: Option<DateTime<Utc>>,
613    /// The conditions that must be met for the rule to fire
614    pub conditions: ConditionGroup,
615    /// The actions to execute when the rule fires
616    pub actions: Vec<ActionType>,
617}
618
619impl Rule {
620    /// Create a new rule with the given name, conditions, and actions
621    pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
622        Self {
623            name,
624            description: None,
625            salience: 0,
626            enabled: true,
627            no_loop: false,
628            lock_on_active: false,
629            agenda_group: None,
630            activation_group: None,
631            date_effective: None,
632            date_expires: None,
633            conditions,
634            actions,
635        }
636    }
637
638    /// Add a description to the rule
639    pub fn with_description(mut self, description: String) -> Self {
640        self.description = Some(description);
641        self
642    }
643
644    /// Set the salience (priority) of the rule
645    pub fn with_salience(mut self, salience: i32) -> Self {
646        self.salience = salience;
647        self
648    }
649
650    /// Set the priority of the rule (alias for salience)
651    pub fn with_priority(mut self, priority: i32) -> Self {
652        self.salience = priority;
653        self
654    }
655
656    /// Enable or disable no-loop behavior for this rule
657    pub fn with_no_loop(mut self, no_loop: bool) -> Self {
658        self.no_loop = no_loop;
659        self
660    }
661
662    /// Enable or disable lock-on-active behavior for this rule
663    pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
664        self.lock_on_active = lock_on_active;
665        self
666    }
667
668    /// Set the agenda group for this rule
669    pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
670        self.agenda_group = Some(agenda_group);
671        self
672    }
673
674    /// Set the activation group for this rule
675    pub fn with_activation_group(mut self, activation_group: String) -> Self {
676        self.activation_group = Some(activation_group);
677        self
678    }
679
680    /// Set the effective date for this rule
681    pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
682        self.date_effective = Some(date_effective);
683        self
684    }
685
686    /// Set the expiration date for this rule
687    pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
688        self.date_expires = Some(date_expires);
689        self
690    }
691
692    /// Parse and set the effective date from ISO string
693    pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
694        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
695        self.date_effective = Some(date);
696        Ok(self)
697    }
698
699    /// Parse and set the expiration date from ISO string
700    pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
701        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
702        self.date_expires = Some(date);
703        Ok(self)
704    }
705
706    /// Check if this rule is active at the given timestamp
707    pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
708        // Check if rule is effective
709        if let Some(effective) = self.date_effective {
710            if timestamp < effective {
711                return false;
712            }
713        }
714
715        // Check if rule has expired
716        if let Some(expires) = self.date_expires {
717            if timestamp >= expires {
718                return false;
719            }
720        }
721
722        true
723    }
724
725    /// Check if this rule is currently active (using current time)
726    pub fn is_active(&self) -> bool {
727        self.is_active_at(Utc::now())
728    }
729
730    /// Check if this rule matches the given facts
731    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
732        self.enabled && self.conditions.evaluate(facts)
733    }
734}
735
736/// Result of rule execution
737#[derive(Debug, Clone)]
738pub struct RuleExecutionResult {
739    /// The name of the rule that was executed
740    pub rule_name: String,
741    /// Whether the rule's conditions matched and it fired
742    pub matched: bool,
743    /// List of actions that were executed
744    pub actions_executed: Vec<String>,
745    /// Time taken to execute the rule in milliseconds
746    pub execution_time_ms: f64,
747}
748
749impl RuleExecutionResult {
750    /// Create a new rule execution result
751    pub fn new(rule_name: String) -> Self {
752        Self {
753            rule_name,
754            matched: false,
755            actions_executed: Vec::new(),
756            execution_time_ms: 0.0,
757        }
758    }
759
760    /// Mark the rule as matched
761    pub fn matched(mut self) -> Self {
762        self.matched = true;
763        self
764    }
765
766    /// Set the actions that were executed
767    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
768        self.actions_executed = actions;
769        self
770    }
771
772    /// Set the execution time in milliseconds
773    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
774        self.execution_time_ms = time_ms;
775        self
776    }
777}
778
779/// Helper function to get nested values from a HashMap
780fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
781    let parts: Vec<&str> = path.split('.').collect();
782    let mut current = data.get(parts[0])?;
783
784    for part in parts.iter().skip(1) {
785        match current {
786            Value::Object(obj) => {
787                current = obj.get(*part)?;
788            }
789            _ => return None,
790        }
791    }
792
793    Some(current)
794}