1use crate::reality_continuum::MergeStrategy;
8use serde_json::Value;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13pub struct ResponseBlender {
14 strategy: MergeStrategy,
16}
17
18impl ResponseBlender {
19 pub fn new(strategy: MergeStrategy) -> Self {
21 Self { strategy }
22 }
23
24 pub fn default() -> Self {
26 Self {
27 strategy: MergeStrategy::FieldLevel,
28 }
29 }
30
31 pub fn blend_responses(&self, mock: &Value, real: &Value, ratio: f64) -> Value {
41 self.blend_responses_with_config(mock, real, ratio, None)
42 }
43
44 pub fn blend_responses_with_config(
55 &self,
56 mock: &Value,
57 real: &Value,
58 global_ratio: f64,
59 field_config: Option<&crate::reality_continuum::FieldRealityConfig>,
60 ) -> Value {
61 let global_ratio = global_ratio.clamp(0.0, 1.0);
62
63 if field_config.is_none() {
65 if global_ratio == 0.0 {
67 return mock.clone();
68 }
69
70 if global_ratio == 1.0 {
72 return real.clone();
73 }
74
75 match self.strategy {
77 MergeStrategy::FieldLevel => self.blend_field_level(mock, real, global_ratio),
78 MergeStrategy::Weighted => self.blend_weighted(mock, real, global_ratio),
79 MergeStrategy::BodyBlend => self.blend_body(mock, real, global_ratio),
80 }
81 } else {
82 self.blend_with_field_config(mock, real, global_ratio, field_config.unwrap())
84 }
85 }
86
87 fn blend_with_field_config(
89 &self,
90 mock: &Value,
91 real: &Value,
92 global_ratio: f64,
93 field_config: &crate::reality_continuum::FieldRealityConfig,
94 ) -> Value {
95 match (mock, real) {
96 (Value::Object(mock_obj), Value::Object(real_obj)) => {
97 let mut result = serde_json::Map::new();
98
99 let mut all_keys = std::collections::HashSet::new();
101 for key in mock_obj.keys() {
102 all_keys.insert(key.clone());
103 }
104 for key in real_obj.keys() {
105 all_keys.insert(key.clone());
106 }
107
108 for key in all_keys {
110 let json_path = key.clone();
111 let mock_val = mock_obj.get(&key);
112 let real_val = real_obj.get(&key);
113
114 let field_ratio =
116 field_config.get_blend_ratio_for_path(&json_path).unwrap_or(global_ratio);
117
118 match (mock_val, real_val) {
119 (Some(m), Some(r)) => {
120 result.insert(
122 key,
123 self.blend_with_field_config(m, r, field_ratio, field_config),
124 );
125 }
126 (Some(m), None) => {
127 if field_ratio < 0.5 {
129 result.insert(key, m.clone());
130 }
131 }
132 (None, Some(r)) => {
133 if field_ratio >= 0.5 {
135 result.insert(key, r.clone());
136 }
137 }
138 (None, None) => {
139 }
141 }
142 }
143
144 Value::Object(result)
145 }
146 (Value::Array(mock_arr), Value::Array(real_arr)) => {
147 match self.strategy {
149 MergeStrategy::FieldLevel => self.blend_field_level(mock, real, global_ratio),
150 MergeStrategy::Weighted => self.blend_weighted(mock, real, global_ratio),
151 MergeStrategy::BodyBlend => self.blend_body(mock, real, global_ratio),
152 }
153 }
154 _ => {
155 if global_ratio < 0.5 {
157 mock.clone()
158 } else {
159 real.clone()
160 }
161 }
162 }
163 }
164
165 fn blend_field_level(&self, mock: &Value, real: &Value, ratio: f64) -> Value {
169 match (mock, real) {
170 (Value::Object(mock_obj), Value::Object(real_obj)) => {
172 let mut result = serde_json::Map::new();
173
174 let mut all_keys = std::collections::HashSet::new();
176 for key in mock_obj.keys() {
177 all_keys.insert(key.clone());
178 }
179 for key in real_obj.keys() {
180 all_keys.insert(key.clone());
181 }
182
183 for key in all_keys {
185 let mock_val = mock_obj.get(&key);
186 let real_val = real_obj.get(&key);
187
188 match (mock_val, real_val) {
189 (Some(m), Some(r)) => {
190 result.insert(key, self.blend_field_level(m, r, ratio));
192 }
193 (Some(m), None) => {
194 if ratio < 0.5 {
196 result.insert(key, m.clone());
197 }
198 }
199 (None, Some(r)) => {
200 if ratio >= 0.5 {
202 result.insert(key, r.clone());
203 }
204 }
205 (None, None) => {
206 }
208 }
209 }
210
211 Value::Object(result)
212 }
213 (Value::Array(mock_arr), Value::Array(real_arr)) => {
215 let mut result = Vec::new();
216
217 let total_len = mock_arr.len().max(real_arr.len());
219 let mock_count = ((1.0 - ratio) * total_len as f64).round() as usize;
220 let real_count = (ratio * total_len as f64).round() as usize;
221
222 for (i, item) in mock_arr.iter().enumerate() {
224 if i < mock_count {
225 result.push(item.clone());
226 }
227 }
228
229 for (i, item) in real_arr.iter().enumerate() {
231 if i < real_count {
232 result.push(item.clone());
233 }
234 }
235
236 if mock_arr.len() != real_arr.len() {
238 let min_len = mock_arr.len().min(real_arr.len());
239 for i in min_len..total_len {
240 if i < mock_arr.len() && i < real_arr.len() {
241 result.push(self.blend_field_level(&mock_arr[i], &real_arr[i], ratio));
243 } else if i < mock_arr.len() {
244 result.push(mock_arr[i].clone());
245 } else if i < real_arr.len() {
246 result.push(real_arr[i].clone());
247 }
248 }
249 }
250
251 Value::Array(result)
252 }
253 (Value::Number(mock_num), Value::Number(real_num)) => {
255 if let (Some(mock_f64), Some(real_f64)) = (mock_num.as_f64(), real_num.as_f64()) {
256 let blended = mock_f64 * (1.0 - ratio) + real_f64 * ratio;
257 Value::Number(serde_json::Number::from_f64(blended).unwrap_or(mock_num.clone()))
258 } else {
259 if ratio < 0.5 {
261 Value::Number(mock_num.clone())
262 } else {
263 Value::Number(real_num.clone())
264 }
265 }
266 }
267 (Value::String(mock_str), Value::String(real_str)) => {
269 if ratio < 0.5 {
270 Value::String(mock_str.clone())
271 } else {
272 Value::String(real_str.clone())
273 }
274 }
275 (Value::Bool(mock_bool), Value::Bool(real_bool)) => {
277 if ratio < 0.5 {
278 Value::Bool(*mock_bool)
279 } else {
280 Value::Bool(*real_bool)
281 }
282 }
283 _ => {
285 if ratio >= 0.5 {
286 real.clone()
287 } else {
288 mock.clone()
289 }
290 }
291 }
292 }
293
294 fn blend_weighted(&self, mock: &Value, real: &Value, ratio: f64) -> Value {
299 if ratio >= 0.5 {
302 real.clone()
303 } else {
304 mock.clone()
305 }
306 }
307
308 fn blend_body(&self, mock: &Value, real: &Value, ratio: f64) -> Value {
312 match (mock, real) {
314 (Value::Object(mock_obj), Value::Object(real_obj)) => {
315 let mut result = serde_json::Map::new();
316
317 let mut all_keys = std::collections::HashSet::new();
319 for key in mock_obj.keys() {
320 all_keys.insert(key.clone());
321 }
322 for key in real_obj.keys() {
323 all_keys.insert(key.clone());
324 }
325
326 for key in all_keys {
328 let mock_val = mock_obj.get(&key);
329 let real_val = real_obj.get(&key);
330
331 match (mock_val, real_val) {
332 (Some(m), Some(r)) => {
333 result.insert(key, self.blend_body(m, r, ratio));
334 }
335 (Some(m), None) => {
336 result.insert(key, m.clone());
337 }
338 (None, Some(r)) => {
339 result.insert(key, r.clone());
340 }
341 (None, None) => {}
342 }
343 }
344
345 Value::Object(result)
346 }
347 (Value::Array(mock_arr), Value::Array(real_arr)) => {
348 let mut result = Vec::new();
350 let max_len = mock_arr.len().max(real_arr.len());
351
352 for i in 0..max_len {
353 if i < mock_arr.len() && i < real_arr.len() {
354 result.push(self.blend_body(&mock_arr[i], &real_arr[i], ratio));
356 } else if i < mock_arr.len() {
357 result.push(mock_arr[i].clone());
358 } else if i < real_arr.len() {
359 result.push(real_arr[i].clone());
360 }
361 }
362
363 Value::Array(result)
364 }
365 (Value::Number(mock_num), Value::Number(real_num)) => {
366 if let (Some(mock_f64), Some(real_f64)) = (mock_num.as_f64(), real_num.as_f64()) {
367 let blended = mock_f64 * (1.0 - ratio) + real_f64 * ratio;
368 Value::Number(serde_json::Number::from_f64(blended).unwrap_or(mock_num.clone()))
369 } else {
370 if ratio < 0.5 {
371 Value::Number(mock_num.clone())
372 } else {
373 Value::Number(real_num.clone())
374 }
375 }
376 }
377 _ => {
378 if ratio >= 0.5 {
379 real.clone()
380 } else {
381 mock.clone()
382 }
383 }
384 }
385 }
386
387 pub fn blend_status_code(&self, mock_status: u16, real_status: u16, ratio: f64) -> u16 {
392 if ratio >= 0.5 {
393 real_status
394 } else {
395 mock_status
396 }
397 }
398
399 pub fn blend_headers(
403 &self,
404 mock_headers: &HashMap<String, String>,
405 real_headers: &HashMap<String, String>,
406 ratio: f64,
407 ) -> HashMap<String, String> {
408 let mut result = HashMap::new();
409
410 let mut all_keys = std::collections::HashSet::new();
412 for key in mock_headers.keys() {
413 all_keys.insert(key.clone());
414 }
415 for key in real_headers.keys() {
416 all_keys.insert(key.clone());
417 }
418
419 for key in all_keys {
421 let mock_val = mock_headers.get(&key);
422 let real_val = real_headers.get(&key);
423
424 match (mock_val, real_val) {
425 (Some(m), Some(r)) => {
426 if ratio >= 0.5 {
428 result.insert(key, r.clone());
429 } else {
430 result.insert(key, m.clone());
431 }
432 }
433 (Some(m), None) => {
434 if ratio < 0.5 {
436 result.insert(key, m.clone());
437 }
438 }
439 (None, Some(r)) => {
440 if ratio >= 0.5 {
442 result.insert(key, r.clone());
443 }
444 }
445 (None, None) => {}
446 }
447 }
448
449 result
450 }
451}
452
453impl Default for ResponseBlender {
454 fn default() -> Self {
455 Self::new(MergeStrategy::FieldLevel)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use serde_json::json;
463
464 #[test]
465 fn test_blend_objects() {
466 let blender = ResponseBlender::default();
467 let mock = json!({
468 "id": 1,
469 "name": "Mock User",
470 "email": "mock@example.com"
471 });
472 let real = json!({
473 "id": 2,
474 "name": "Real User",
475 "status": "active"
476 });
477
478 let blended = blender.blend_responses(&mock, &real, 0.5);
479 assert!(blended.is_object());
480 }
481
482 #[test]
483 fn test_blend_arrays() {
484 let blender = ResponseBlender::default();
485 let mock = json!([1, 2, 3]);
486 let real = json!([4, 5, 6]);
487
488 let blended = blender.blend_responses(&mock, &real, 0.5);
489 assert!(blended.is_array());
490 }
491
492 #[test]
493 fn test_blend_numbers() {
494 let blender = ResponseBlender::default();
495 let mock = json!(10.0);
496 let real = json!(20.0);
497
498 let blended = blender.blend_responses(&mock, &real, 0.5);
499 if let Value::Number(n) = blended {
500 if let Some(f) = n.as_f64() {
501 assert!((f - 15.0).abs() < 0.1); }
503 }
504 }
505
506 #[test]
507 fn test_blend_status_code() {
508 let blender = ResponseBlender::default();
509 assert_eq!(blender.blend_status_code(200, 404, 0.3), 200); assert_eq!(blender.blend_status_code(200, 404, 0.7), 404); }
512
513 #[test]
514 fn test_blend_headers() {
515 let blender = ResponseBlender::default();
516 let mut mock_headers = HashMap::new();
517 mock_headers.insert("X-Mock".to_string(), "true".to_string());
518 mock_headers.insert("Content-Type".to_string(), "application/json".to_string());
519
520 let mut real_headers = HashMap::new();
521 real_headers.insert("X-Real".to_string(), "true".to_string());
522 real_headers.insert("Content-Type".to_string(), "application/xml".to_string());
523
524 let blended = blender.blend_headers(&mock_headers, &real_headers, 0.7);
525 assert_eq!(blended.get("Content-Type"), Some(&"application/xml".to_string()));
526 assert_eq!(blended.get("X-Real"), Some(&"true".to_string()));
527 }
528
529 #[test]
530 fn test_blend_nested_objects() {
531 let blender = ResponseBlender::default();
532 let mock = json!({
533 "user": {
534 "id": 1,
535 "name": "Mock User",
536 "email": "mock@example.com"
537 },
538 "metadata": {
539 "source": "mock"
540 }
541 });
542 let real = json!({
543 "user": {
544 "id": 2,
545 "name": "Real User",
546 "status": "active"
547 },
548 "metadata": {
549 "source": "real",
550 "timestamp": "2025-01-01T00:00:00Z"
551 }
552 });
553
554 let blended = blender.blend_responses(&mock, &real, 0.5);
555 assert!(blended.is_object());
556 assert!(blended.get("user").is_some());
557 assert!(blended.get("metadata").is_some());
558 }
559
560 #[test]
561 fn test_blend_ratio_boundaries() {
562 let blender = ResponseBlender::default();
563 let mock = json!({"value": "mock"});
564 let real = json!({"value": "real"});
565
566 let result_0 = blender.blend_responses(&mock, &real, 0.0);
568 assert_eq!(result_0, mock);
569
570 let result_1 = blender.blend_responses(&mock, &real, 1.0);
572 assert_eq!(result_1, real);
573 }
574
575 #[test]
576 fn test_blend_mixed_types() {
577 let blender = ResponseBlender::default();
578 let mock = json!({
579 "string": "mock",
580 "number": 10,
581 "boolean": true,
582 "array": [1, 2, 3]
583 });
584 let real = json!({
585 "string": "real",
586 "number": 20,
587 "boolean": false,
588 "array": [4, 5, 6]
589 });
590
591 let blended = blender.blend_responses(&mock, &real, 0.5);
592 assert!(blended.is_object());
593 assert!(blended.get("string").is_some());
594 assert!(blended.get("number").is_some());
595 assert!(blended.get("boolean").is_some());
596 assert!(blended.get("array").is_some());
597 }
598}