iam_rs/evaluation/
context.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Represents different types of context values
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum ContextValue {
9    /// String value (e.g., user ID, IP address)
10    String(String),
11    /// Boolean value (e.g., MFA present)
12    Boolean(bool),
13    /// Numeric value (e.g., MFA age in seconds, epoch time)
14    Number(f64),
15    /// DateTime value (e.g., request time)
16    DateTime(DateTime<Utc>),
17}
18
19impl ContextValue {
20    /// Converts the context value to a string representation
21    pub fn as_string(&self) -> Option<&String> {
22        match self {
23            ContextValue::String(s) => Some(s),
24            _ => None,
25        }
26    }
27
28    /// Converts the context value to a boolean
29    pub fn as_boolean(&self) -> Option<bool> {
30        match self {
31            ContextValue::Boolean(b) => Some(*b),
32            _ => None,
33        }
34    }
35
36    /// Converts the context value to a number
37    pub fn as_number(&self) -> Option<f64> {
38        match self {
39            ContextValue::Number(n) => Some(*n),
40            _ => None,
41        }
42    }
43}
44
45/// Context for IAM evaluation containing key-value pairs
46#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
47pub struct Context {
48    /// Context keys and their values
49    pub data: HashMap<String, ContextValue>,
50}
51
52impl Context {
53    /// Creates a new empty context
54    pub fn new() -> Self {
55        Self {
56            data: HashMap::new(),
57        }
58    }
59
60    /// Creates a context with initial data
61    pub fn with_data(data: HashMap<String, ContextValue>) -> Self {
62        Self { data }
63    }
64
65    /// Get a context value by key
66    pub fn get(&self, key: &str) -> Option<&ContextValue> {
67        self.data.get(key)
68    }
69
70    /// Insert a context value
71    pub fn insert(&mut self, key: String, value: ContextValue) {
72        self.data.insert(key, value);
73    }
74
75    /// Adds a string context value
76    pub fn with_string<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
77        self.data
78            .insert(key.into(), ContextValue::String(value.into()));
79        self
80    }
81
82    /// Adds a boolean context value
83    pub fn with_boolean<K: Into<String>>(mut self, key: K, value: bool) -> Self {
84        self.data.insert(key.into(), ContextValue::Boolean(value));
85        self
86    }
87
88    /// Adds a numeric context value
89    pub fn with_number<K: Into<String>>(mut self, key: K, value: f64) -> Self {
90        self.data.insert(key.into(), ContextValue::Number(value));
91        self
92    }
93
94    /// Checks if a context key exists
95    pub fn has_key(&self, key: &str) -> bool {
96        self.data.contains_key(key)
97    }
98
99    /// Gets all context keys
100    pub fn keys(&self) -> Vec<&String> {
101        self.data.keys().collect()
102    }
103
104    /// Extends the context with another context
105    pub fn extend(&mut self, other: Context) {
106        self.data.extend(other.data);
107    }
108}
109
110impl Default for Context {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_context_creation() {
122        let context = Context::new()
123            .with_string("key1", "value1")
124            .with_boolean("key2", true)
125            .with_number("key3", 42.0);
126
127        assert_eq!(context.get("key1").unwrap().as_string().unwrap(), "value1");
128        assert_eq!(context.get("key2").unwrap().as_boolean().unwrap(), true);
129        assert_eq!(context.get("key3").unwrap().as_number().unwrap(), 42.0);
130    }
131}