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 if ratio < 0.5 {
370 Value::Number(mock_num.clone())
371 } else {
372 Value::Number(real_num.clone())
373 }
374 }
375 _ => {
376 if ratio >= 0.5 {
377 real.clone()
378 } else {
379 mock.clone()
380 }
381 }
382 }
383 }
384
385 pub fn blend_status_code(&self, mock_status: u16, real_status: u16, ratio: f64) -> u16 {
390 if ratio >= 0.5 {
391 real_status
392 } else {
393 mock_status
394 }
395 }
396
397 pub fn blend_headers(
401 &self,
402 mock_headers: &HashMap<String, String>,
403 real_headers: &HashMap<String, String>,
404 ratio: f64,
405 ) -> HashMap<String, String> {
406 let mut result = HashMap::new();
407
408 let mut all_keys = std::collections::HashSet::new();
410 for key in mock_headers.keys() {
411 all_keys.insert(key.clone());
412 }
413 for key in real_headers.keys() {
414 all_keys.insert(key.clone());
415 }
416
417 for key in all_keys {
419 let mock_val = mock_headers.get(&key);
420 let real_val = real_headers.get(&key);
421
422 match (mock_val, real_val) {
423 (Some(m), Some(r)) => {
424 if ratio >= 0.5 {
426 result.insert(key, r.clone());
427 } else {
428 result.insert(key, m.clone());
429 }
430 }
431 (Some(m), None) => {
432 if ratio < 0.5 {
434 result.insert(key, m.clone());
435 }
436 }
437 (None, Some(r)) => {
438 if ratio >= 0.5 {
440 result.insert(key, r.clone());
441 }
442 }
443 (None, None) => {}
444 }
445 }
446
447 result
448 }
449}
450
451impl Default for ResponseBlender {
452 fn default() -> Self {
453 Self::new(MergeStrategy::FieldLevel)
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use super::*;
460 use serde_json::json;
461
462 #[test]
463 fn test_blend_objects() {
464 let blender = ResponseBlender::default();
465 let mock = json!({
466 "id": 1,
467 "name": "Mock User",
468 "email": "mock@example.com"
469 });
470 let real = json!({
471 "id": 2,
472 "name": "Real User",
473 "status": "active"
474 });
475
476 let blended = blender.blend_responses(&mock, &real, 0.5);
477 assert!(blended.is_object());
478 }
479
480 #[test]
481 fn test_blend_arrays() {
482 let blender = ResponseBlender::default();
483 let mock = json!([1, 2, 3]);
484 let real = json!([4, 5, 6]);
485
486 let blended = blender.blend_responses(&mock, &real, 0.5);
487 assert!(blended.is_array());
488 }
489
490 #[test]
491 fn test_blend_numbers() {
492 let blender = ResponseBlender::default();
493 let mock = json!(10.0);
494 let real = json!(20.0);
495
496 let blended = blender.blend_responses(&mock, &real, 0.5);
497 if let Value::Number(n) = blended {
498 if let Some(f) = n.as_f64() {
499 assert!((f - 15.0).abs() < 0.1); }
501 }
502 }
503
504 #[test]
505 fn test_blend_status_code() {
506 let blender = ResponseBlender::default();
507 assert_eq!(blender.blend_status_code(200, 404, 0.3), 200); assert_eq!(blender.blend_status_code(200, 404, 0.7), 404); }
510
511 #[test]
512 fn test_blend_headers() {
513 let blender = ResponseBlender::default();
514 let mut mock_headers = HashMap::new();
515 mock_headers.insert("X-Mock".to_string(), "true".to_string());
516 mock_headers.insert("Content-Type".to_string(), "application/json".to_string());
517
518 let mut real_headers = HashMap::new();
519 real_headers.insert("X-Real".to_string(), "true".to_string());
520 real_headers.insert("Content-Type".to_string(), "application/xml".to_string());
521
522 let blended = blender.blend_headers(&mock_headers, &real_headers, 0.7);
523 assert_eq!(blended.get("Content-Type"), Some(&"application/xml".to_string()));
524 assert_eq!(blended.get("X-Real"), Some(&"true".to_string()));
525 }
526
527 #[test]
528 fn test_blend_nested_objects() {
529 let blender = ResponseBlender::default();
530 let mock = json!({
531 "user": {
532 "id": 1,
533 "name": "Mock User",
534 "email": "mock@example.com"
535 },
536 "metadata": {
537 "source": "mock"
538 }
539 });
540 let real = json!({
541 "user": {
542 "id": 2,
543 "name": "Real User",
544 "status": "active"
545 },
546 "metadata": {
547 "source": "real",
548 "timestamp": "2025-01-01T00:00:00Z"
549 }
550 });
551
552 let blended = blender.blend_responses(&mock, &real, 0.5);
553 assert!(blended.is_object());
554 assert!(blended.get("user").is_some());
555 assert!(blended.get("metadata").is_some());
556 }
557
558 #[test]
559 fn test_blend_ratio_boundaries() {
560 let blender = ResponseBlender::default();
561 let mock = json!({"value": "mock"});
562 let real = json!({"value": "real"});
563
564 let result_0 = blender.blend_responses(&mock, &real, 0.0);
566 assert_eq!(result_0, mock);
567
568 let result_1 = blender.blend_responses(&mock, &real, 1.0);
570 assert_eq!(result_1, real);
571 }
572
573 #[test]
574 fn test_blend_mixed_types() {
575 let blender = ResponseBlender::default();
576 let mock = json!({
577 "string": "mock",
578 "number": 10,
579 "boolean": true,
580 "array": [1, 2, 3]
581 });
582 let real = json!({
583 "string": "real",
584 "number": 20,
585 "boolean": false,
586 "array": [4, 5, 6]
587 });
588
589 let blended = blender.blend_responses(&mock, &real, 0.5);
590 assert!(blended.is_object());
591 assert!(blended.get("string").is_some());
592 assert!(blended.get("number").is_some());
593 assert!(blended.get("boolean").is_some());
594 assert!(blended.get("array").is_some());
595 }
596}