mockforge_data/
token_resolver.rs

1//! Token-based response templating
2//!
3//! This module provides token resolution for dynamic response generation.
4//! Supports $random, $faker, and $ai tokens for intelligent mock data.
5
6use crate::{
7    faker::EnhancedFaker,
8    rag::{RagConfig, RagEngine},
9};
10use mockforge_core::{Error, Result};
11use rand::Rng;
12use regex::Regex;
13use serde_json::{json, Value};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18/// Token types supported by the resolver
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum TokenType {
21    /// Random value generation: $random.int, $random.float, $random.uuid, etc.
22    Random(String),
23    /// Faker data generation: $faker.name, $faker.email, $faker.address, etc.
24    Faker(String),
25    /// AI-generated content: $ai(prompt)
26    Ai(String),
27}
28
29/// Token resolver for dynamic response generation
30pub struct TokenResolver {
31    /// Faker instance for data generation
32    faker: Arc<RwLock<EnhancedFaker>>,
33    /// RAG engine for AI generation
34    rag_engine: Option<Arc<RwLock<RagEngine>>>,
35    /// Cache for resolved tokens
36    cache: Arc<RwLock<HashMap<String, Value>>>,
37}
38
39impl TokenResolver {
40    /// Create a new token resolver
41    pub fn new() -> Self {
42        Self {
43            faker: Arc::new(RwLock::new(EnhancedFaker::new())),
44            rag_engine: None,
45            cache: Arc::new(RwLock::new(HashMap::new())),
46        }
47    }
48
49    /// Create a new token resolver with RAG support
50    pub fn with_rag(rag_config: RagConfig) -> Self {
51        Self {
52            faker: Arc::new(RwLock::new(EnhancedFaker::new())),
53            rag_engine: Some(Arc::new(RwLock::new(RagEngine::new(rag_config)))),
54            cache: Arc::new(RwLock::new(HashMap::new())),
55        }
56    }
57
58    /// Resolve all tokens in a JSON value
59    pub async fn resolve(&self, value: &Value) -> Result<Value> {
60        match value {
61            Value::String(s) => self.resolve_string(s).await,
62            Value::Array(arr) => {
63                let mut resolved = Vec::new();
64                for item in arr {
65                    resolved.push(Box::pin(self.resolve(item)).await?);
66                }
67                Ok(Value::Array(resolved))
68            }
69            Value::Object(obj) => {
70                let mut resolved = serde_json::Map::new();
71                for (key, val) in obj {
72                    resolved.insert(key.clone(), Box::pin(self.resolve(val)).await?);
73                }
74                Ok(Value::Object(resolved))
75            }
76            _ => Ok(value.clone()),
77        }
78    }
79
80    /// Resolve tokens in a string value
81    async fn resolve_string(&self, s: &str) -> Result<Value> {
82        // Check if the entire string is a single token
83        if let Some(token) = self.parse_token(s) {
84            return self.resolve_token(&token).await;
85        }
86
87        // Check for embedded tokens in the string
88        let token_regex =
89            Regex::new(r"\$(?:random|faker|ai)(?:\.[a-zA-Z_][a-zA-Z0-9_]*|\([^)]*\))?")
90                .map_err(|e| Error::generic(format!("Regex error: {}", e)))?;
91
92        if token_regex.is_match(s) {
93            let mut result = s.to_string();
94            for cap in token_regex.captures_iter(s) {
95                if let Some(token_str) = cap.get(0) {
96                    if let Some(token) = self.parse_token(token_str.as_str()) {
97                        let resolved = self.resolve_token(&token).await?;
98                        let resolved_str = match resolved {
99                            Value::String(s) => s,
100                            _ => resolved.to_string(),
101                        };
102                        result = result.replace(token_str.as_str(), &resolved_str);
103                    }
104                }
105            }
106            Ok(Value::String(result))
107        } else {
108            Ok(Value::String(s.to_string()))
109        }
110    }
111
112    /// Parse a token from a string
113    fn parse_token(&self, s: &str) -> Option<TokenType> {
114        let s = s.trim();
115
116        // Parse $random.* tokens
117        if let Some(suffix) = s.strip_prefix("$random.") {
118            return Some(TokenType::Random(suffix.to_string()));
119        }
120
121        // Parse $faker.* tokens
122        if let Some(suffix) = s.strip_prefix("$faker.") {
123            return Some(TokenType::Faker(suffix.to_string()));
124        }
125
126        // Parse $ai(...) tokens
127        if s.starts_with("$ai(") && s.ends_with(')') {
128            let prompt = s.strip_prefix("$ai(")?.strip_suffix(')')?;
129            return Some(TokenType::Ai(prompt.trim().to_string()));
130        }
131
132        None
133    }
134
135    /// Resolve a single token
136    async fn resolve_token(&self, token: &TokenType) -> Result<Value> {
137        match token {
138            TokenType::Random(kind) => self.resolve_random(kind).await,
139            TokenType::Faker(kind) => self.resolve_faker(kind).await,
140            TokenType::Ai(prompt) => self.resolve_ai(prompt).await,
141        }
142    }
143
144    /// Resolve a $random token
145    async fn resolve_random(&self, kind: &str) -> Result<Value> {
146        match kind {
147            "int" | "integer" => Ok(json!(rand::rng().random_range(0..1000))),
148            "int.small" => Ok(json!(rand::rng().random_range(0..100))),
149            "int.large" => Ok(json!(rand::rng().random_range(0..1_000_000))),
150            "float" | "number" => Ok(json!(rand::rng().random_range(0.0..1000.0))),
151            "bool" | "boolean" => Ok(json!(rand::rng().random_bool(0.5))),
152            "uuid" => Ok(json!(uuid::Uuid::new_v4().to_string())),
153            "hex" => {
154                let bytes: [u8; 16] = rand::rng().random();
155                Ok(json!(hex::encode(bytes)))
156            }
157            "hex.short" => {
158                let bytes: [u8; 4] = rand::rng().random();
159                Ok(json!(hex::encode(bytes)))
160            }
161            "alphanumeric" => {
162                let s: String = (0..10)
163                    .map(|_| {
164                        let c: u8 = rand::rng().random_range(b'a'..=b'z');
165                        c as char
166                    })
167                    .collect();
168                Ok(json!(s))
169            }
170            "choice" => {
171                let choices = ["option1", "option2", "option3"];
172                let idx = rand::rng().random_range(0..choices.len());
173                Ok(json!(choices[idx]))
174            }
175            _ => Err(Error::generic(format!("Unknown random type: {}", kind))),
176        }
177    }
178
179    /// Resolve a $faker token
180    async fn resolve_faker(&self, kind: &str) -> Result<Value> {
181        let mut faker = self.faker.write().await;
182
183        let value = match kind {
184            // Person
185            "name" => json!(faker.name()),
186            "email" => json!(faker.email()),
187            "phone" | "phone_number" => json!(faker.phone()),
188
189            // Address
190            "address" => json!(faker.address()),
191
192            // Company
193            "company" => json!(faker.company()),
194
195            // Internet
196            "url" => json!(faker.url()),
197            "ipv4" | "ip" => json!(faker.ip_address()),
198
199            // Date/Time
200            "date" | "datetime" | "timestamp" | "iso8601" => json!(faker.date_iso()),
201
202            // Lorem
203            "word" => json!(faker.word()),
204            "words" => json!(faker.words(5)),
205            "sentence" => json!(faker.sentence()),
206            "paragraph" => json!(faker.paragraph()),
207
208            // ID
209            "uuid" => json!(faker.uuid()),
210
211            // Use generate_by_type for other types
212            _ => faker.generate_by_type(kind),
213        };
214
215        Ok(value)
216    }
217
218    /// Resolve an $ai token
219    async fn resolve_ai(&self, prompt: &str) -> Result<Value> {
220        // Check cache first
221        let cache_key = format!("ai:{}", prompt);
222        {
223            let cache = self.cache.read().await;
224            if let Some(cached) = cache.get(&cache_key) {
225                return Ok(cached.clone());
226            }
227        }
228
229        // Generate using RAG engine if available
230        if let Some(rag_engine) = &self.rag_engine {
231            let engine = rag_engine.write().await;
232            let response = engine.generate_text(prompt).await?;
233
234            // Try to parse as JSON
235            let value = if let Ok(json_value) = serde_json::from_str::<Value>(&response) {
236                json_value
237            } else {
238                json!(response)
239            };
240
241            // Cache the result
242            let mut cache = self.cache.write().await;
243            cache.insert(cache_key, value.clone());
244
245            Ok(value)
246        } else {
247            // Fallback: return a placeholder if no RAG engine available
248            Ok(json!(format!("[AI: {}]", prompt)))
249        }
250    }
251
252    /// Clear the cache
253    pub async fn clear_cache(&self) {
254        let mut cache = self.cache.write().await;
255        cache.clear();
256    }
257
258    /// Get cache size
259    pub async fn cache_size(&self) -> usize {
260        let cache = self.cache.read().await;
261        cache.len()
262    }
263}
264
265impl Default for TokenResolver {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271/// Resolve tokens in a value using a default resolver
272pub async fn resolve_tokens(value: &Value) -> Result<Value> {
273    let resolver = TokenResolver::new();
274    resolver.resolve(value).await
275}
276
277/// Resolve tokens with RAG support
278pub async fn resolve_tokens_with_rag(value: &Value, rag_config: RagConfig) -> Result<Value> {
279    let resolver = TokenResolver::with_rag(rag_config);
280    resolver.resolve(value).await
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_parse_token_random() {
289        let resolver = TokenResolver::new();
290        assert_eq!(resolver.parse_token("$random.int"), Some(TokenType::Random("int".to_string())));
291        assert_eq!(
292            resolver.parse_token("$random.uuid"),
293            Some(TokenType::Random("uuid".to_string()))
294        );
295    }
296
297    #[test]
298    fn test_parse_token_faker() {
299        let resolver = TokenResolver::new();
300        assert_eq!(resolver.parse_token("$faker.name"), Some(TokenType::Faker("name".to_string())));
301        assert_eq!(
302            resolver.parse_token("$faker.email"),
303            Some(TokenType::Faker("email".to_string()))
304        );
305    }
306
307    #[test]
308    fn test_parse_token_ai() {
309        let resolver = TokenResolver::new();
310        assert_eq!(
311            resolver.parse_token("$ai(generate customer data)"),
312            Some(TokenType::Ai("generate customer data".to_string()))
313        );
314    }
315
316    #[test]
317    fn test_parse_token_invalid() {
318        let resolver = TokenResolver::new();
319        assert_eq!(resolver.parse_token("invalid"), None);
320        assert_eq!(resolver.parse_token("$invalid"), None);
321    }
322
323    #[tokio::test]
324    async fn test_resolve_random_int() {
325        let resolver = TokenResolver::new();
326        let result = resolver.resolve_random("int").await.unwrap();
327        assert!(result.is_number());
328    }
329
330    #[tokio::test]
331    async fn test_resolve_random_uuid() {
332        let resolver = TokenResolver::new();
333        let result = resolver.resolve_random("uuid").await.unwrap();
334        assert!(result.is_string());
335        let uuid_str = result.as_str().unwrap();
336        assert!(uuid::Uuid::parse_str(uuid_str).is_ok());
337    }
338
339    #[tokio::test]
340    async fn test_resolve_faker_name() {
341        let resolver = TokenResolver::new();
342        let result = resolver.resolve_faker("name").await.unwrap();
343        assert!(result.is_string());
344    }
345
346    #[tokio::test]
347    async fn test_resolve_faker_email() {
348        let resolver = TokenResolver::new();
349        let result = resolver.resolve_faker("email").await.unwrap();
350        assert!(result.is_string());
351        let email = result.as_str().unwrap();
352        assert!(email.contains('@'));
353    }
354
355    #[tokio::test]
356    async fn test_resolve_simple_string() {
357        let resolver = TokenResolver::new();
358        let value = json!("$random.uuid");
359        let result = resolver.resolve(&value).await.unwrap();
360        assert!(result.is_string());
361    }
362
363    #[tokio::test]
364    async fn test_resolve_object() {
365        let resolver = TokenResolver::new();
366        let value = json!({
367            "id": "$random.uuid",
368            "name": "$faker.name",
369            "email": "$faker.email"
370        });
371        let result = resolver.resolve(&value).await.unwrap();
372        assert!(result.is_object());
373        assert!(result["id"].is_string());
374        assert!(result["name"].is_string());
375        assert!(result["email"].is_string());
376    }
377
378    #[tokio::test]
379    async fn test_resolve_array() {
380        let resolver = TokenResolver::new();
381        let value = json!(["$random.uuid", "$faker.name"]);
382        let result = resolver.resolve(&value).await.unwrap();
383        assert!(result.is_array());
384        let arr = result.as_array().unwrap();
385        assert_eq!(arr.len(), 2);
386    }
387
388    #[tokio::test]
389    async fn test_resolve_nested() {
390        let resolver = TokenResolver::new();
391        let value = json!({
392            "user": {
393                "id": "$random.uuid",
394                "profile": {
395                    "name": "$faker.name",
396                    "email": "$faker.email"
397                }
398            }
399        });
400        let result = resolver.resolve(&value).await.unwrap();
401        assert!(result["user"]["id"].is_string());
402        assert!(result["user"]["profile"]["name"].is_string());
403        assert!(result["user"]["profile"]["email"].is_string());
404    }
405
406    #[tokio::test]
407    async fn test_cache() {
408        let resolver = TokenResolver::new();
409        assert_eq!(resolver.cache_size().await, 0);
410
411        // The cache is used internally for AI tokens
412        // For now just verify the cache methods work
413        resolver.clear_cache().await;
414        assert_eq!(resolver.cache_size().await, 0);
415    }
416}