1use crate::Result;
10use crate::domain::value_objects::{JsonPath, Priority};
11use serde_json::{Map as JsonMap, Value as JsonValue};
12use std::collections::VecDeque;
13
14mod serde_priority {
16 use crate::domain::value_objects::Priority;
17 use serde::{Serialize, Serializer};
18
19 pub fn serialize<S>(priority: &Priority, serializer: S) -> Result<S::Ok, S::Error>
20 where
21 S: Serializer,
22 {
23 priority.value().serialize(serializer)
24 }
25}
26
27#[derive(Debug, Clone, serde::Serialize)]
29pub struct JsonPatch {
30 pub path: JsonPath,
32 pub operation: PatchOperation,
34 #[serde(with = "serde_priority")]
36 pub priority: Priority,
37}
38
39#[derive(Debug, Clone, serde::Serialize)]
41pub enum PatchOperation {
42 Set {
44 value: JsonValue,
46 },
47 Append {
49 values: Vec<JsonValue>,
51 },
52 Replace {
54 value: JsonValue,
56 },
57 Remove,
59}
60
61#[derive(Debug, Clone, serde::Serialize)]
63pub enum PriorityStreamFrame {
64 Skeleton {
66 data: JsonValue,
68 #[serde(with = "serde_priority")]
70 priority: Priority,
71 complete: bool,
73 },
74 Patch {
76 patches: Vec<JsonPatch>,
78 #[serde(with = "serde_priority")]
80 priority: Priority,
81 },
82 Complete {
84 checksum: Option<u64>,
86 },
87}
88
89pub struct PriorityStreamer {
91 config: StreamerConfig,
92}
93
94#[derive(Debug, Clone)]
96pub struct StreamerConfig {
97 pub detect_semantics: bool,
99 pub max_patch_size: usize,
101 pub priority_threshold: Priority,
103}
104
105impl Default for StreamerConfig {
106 fn default() -> Self {
107 Self {
108 detect_semantics: true,
109 max_patch_size: 100,
110 priority_threshold: Priority::LOW,
111 }
112 }
113}
114
115impl PriorityStreamer {
116 pub fn new() -> Self {
118 Self::with_config(StreamerConfig::default())
119 }
120
121 pub fn with_config(config: StreamerConfig) -> Self {
123 Self { config }
124 }
125
126 pub fn analyze(&self, json: &JsonValue) -> Result<StreamingPlan> {
128 let mut plan = StreamingPlan::new();
129
130 let skeleton = self.generate_skeleton(json);
132 plan.frames.push_back(PriorityStreamFrame::Skeleton {
133 data: skeleton,
134 priority: Priority::CRITICAL,
135 complete: false,
136 });
137
138 let mut patches = Vec::new();
140 self.extract_patches(json, &JsonPath::root(), &mut patches)?;
141
142 patches.sort_by_key(|patch| std::cmp::Reverse(patch.priority));
144
145 let mut current_priority = Priority::CRITICAL;
146 let mut current_batch = Vec::new();
147
148 for patch in patches {
149 if patch.priority != current_priority && !current_batch.is_empty() {
150 plan.frames.push_back(PriorityStreamFrame::Patch {
151 patches: current_batch,
152 priority: current_priority,
153 });
154 current_batch = Vec::new();
155 }
156 current_priority = patch.priority;
157 current_batch.push(patch);
158
159 if current_batch.len() >= self.config.max_patch_size {
160 plan.frames.push_back(PriorityStreamFrame::Patch {
161 patches: current_batch,
162 priority: current_priority,
163 });
164 current_batch = Vec::new();
165 }
166 }
167
168 if !current_batch.is_empty() {
170 plan.frames.push_back(PriorityStreamFrame::Patch {
171 patches: current_batch,
172 priority: current_priority,
173 });
174 }
175
176 plan.frames
178 .push_back(PriorityStreamFrame::Complete { checksum: None });
179
180 Ok(plan)
181 }
182
183 fn generate_skeleton(&self, json: &JsonValue) -> JsonValue {
185 match json {
186 JsonValue::Object(map) => {
187 let mut skeleton = JsonMap::new();
188 for (key, value) in map {
189 skeleton.insert(
190 key.clone(),
191 match value {
192 JsonValue::Array(_) => JsonValue::Array(vec![]),
193 JsonValue::Object(_) => self.generate_skeleton(value),
194 JsonValue::String(_) => JsonValue::Null,
195 JsonValue::Number(_) => JsonValue::Number(0.into()),
196 JsonValue::Bool(_) => JsonValue::Bool(false),
197 JsonValue::Null => JsonValue::Null,
198 },
199 );
200 }
201 JsonValue::Object(skeleton)
202 }
203 JsonValue::Array(_) => JsonValue::Array(vec![]),
204 _ => JsonValue::Null,
205 }
206 }
207
208 fn skeletonize_arrays(value: &JsonValue) -> JsonValue {
216 match value {
217 JsonValue::Object(map) => {
218 let skeleton = map
219 .iter()
220 .map(|(key, v)| match JsonPath::root().append_key(key) {
221 Ok(_) => (key.clone(), Self::skeletonize_arrays(v)),
222 Err(_) => (key.clone(), v.clone()),
223 })
224 .collect();
225 JsonValue::Object(skeleton)
226 }
227 JsonValue::Array(_) => JsonValue::Array(vec![]),
228 other => other.clone(),
229 }
230 }
231
232 fn extract_patches(
234 &self,
235 json: &JsonValue,
236 current_path: &JsonPath,
237 patches: &mut Vec<JsonPatch>,
238 ) -> Result<()> {
239 match json {
240 JsonValue::Object(map) => {
241 for (key, value) in map {
242 let Ok(field_path) = current_path.append_key(key) else {
245 continue;
246 };
247 let own_priority = self.calculate_field_priority(&field_path, key, value);
248
249 let mut child_patches = Vec::new();
250 self.extract_patches(value, &field_path, &mut child_patches)?;
251
252 let append_ceiling = child_patches
256 .iter()
257 .filter(|p| matches!(p.operation, PatchOperation::Append { .. }))
258 .map(|p| p.priority)
259 .max();
260 let priority =
261 append_ceiling.map_or(own_priority, |ceiling| own_priority.max(ceiling));
262
263 patches.push(JsonPatch {
264 path: field_path.clone(),
265 operation: PatchOperation::Set {
266 value: Self::skeletonize_arrays(value),
267 },
268 priority,
269 });
270
271 patches.extend(child_patches);
272 }
273 }
274 JsonValue::Array(arr) => {
275 if arr.len() > 10 {
277 let priority = self.calculate_array_priority(current_path, arr);
283 for chunk in arr.chunks(self.config.max_patch_size) {
284 patches.push(JsonPatch {
285 path: current_path.clone(),
286 operation: PatchOperation::Append {
287 values: chunk.to_vec(),
288 },
289 priority,
290 });
291 }
292 } else if !arr.is_empty() {
293 patches.push(JsonPatch {
294 path: current_path.clone(),
295 operation: PatchOperation::Append {
296 values: arr.clone(),
297 },
298 priority: self.calculate_array_priority(current_path, arr),
299 });
300 }
301 }
302 _ => {
303 }
305 }
306
307 Ok(())
308 }
309
310 fn calculate_field_priority(&self, _path: &JsonPath, key: &str, value: &JsonValue) -> Priority {
312 if matches!(key, "id" | "uuid" | "status" | "type" | "kind") {
314 return Priority::CRITICAL;
315 }
316
317 if matches!(key, "name" | "title" | "label" | "email" | "username") {
319 return Priority::HIGH;
320 }
321
322 if key.contains("analytics") || key.contains("stats") || key.contains("meta") {
324 return Priority::LOW;
325 }
326
327 if matches!(key, "reviews" | "comments" | "logs" | "history") {
328 return Priority::BACKGROUND;
329 }
330
331 match value {
333 JsonValue::Array(arr) if arr.len() > 100 => Priority::BACKGROUND,
334 JsonValue::Object(obj) if obj.contains_key("timestamp") => Priority::MEDIUM,
335 JsonValue::String(s) if s.len() > 1000 => Priority::LOW,
336 _ => Priority::MEDIUM,
337 }
338 }
339
340 fn calculate_array_priority(&self, path: &JsonPath, elements: &[JsonValue]) -> Priority {
342 if elements.len() > 50 {
344 return Priority::BACKGROUND;
345 }
346
347 if let Some(last_key) = path.last_key() {
349 if matches!(last_key, "reviews" | "comments" | "logs") {
350 return Priority::BACKGROUND;
351 }
352 if matches!(last_key, "items" | "data" | "results") {
353 return Priority::MEDIUM;
354 }
355 }
356
357 Priority::MEDIUM
358 }
359}
360
361#[derive(Debug)]
363pub struct StreamingPlan {
364 pub frames: VecDeque<PriorityStreamFrame>,
366}
367
368impl Default for StreamingPlan {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374impl StreamingPlan {
375 pub fn new() -> Self {
377 Self {
378 frames: VecDeque::new(),
379 }
380 }
381
382 pub fn next_frame(&mut self) -> Option<PriorityStreamFrame> {
384 self.frames.pop_front()
385 }
386
387 pub fn is_complete(&self) -> bool {
389 self.frames.is_empty()
390 }
391
392 pub fn remaining_frames(&self) -> usize {
394 self.frames.len()
395 }
396
397 pub fn frames(&self) -> impl Iterator<Item = &PriorityStreamFrame> {
399 self.frames.iter()
400 }
401}
402
403impl Default for PriorityStreamer {
404 fn default() -> Self {
405 Self::new()
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::stream::reconstruction::JsonReconstructor;
413 use serde_json::json;
414
415 fn round_trip(streamer: &PriorityStreamer, payload: &JsonValue) -> JsonValue {
420 let plan = streamer.analyze(payload).unwrap();
421 let mut reconstructor = JsonReconstructor::new();
422 for frame in plan.frames {
423 reconstructor.add_frame(frame);
424 }
425 reconstructor.process_all_frames().unwrap();
426 reconstructor.current_state().clone()
427 }
428
429 #[test]
430 fn test_json_path_creation() {
431 let path = JsonPath::root();
432 assert_eq!(path.to_json_pointer(), "/");
433
434 let path = path
435 .append_key("users")
436 .unwrap()
437 .append_index(0)
438 .append_key("name")
439 .unwrap();
440 assert_eq!(path.to_json_pointer(), "/users/0/name");
441 }
442
443 #[test]
444 fn test_priority_comparison() {
445 assert!(Priority::CRITICAL > Priority::HIGH);
446 assert!(Priority::HIGH > Priority::MEDIUM);
447 assert!(Priority::MEDIUM > Priority::LOW);
448 assert!(Priority::LOW > Priority::BACKGROUND);
449 }
450
451 #[test]
452 fn test_skeleton_generation() {
453 let streamer = PriorityStreamer::new();
454 let json = json!({
455 "name": "John",
456 "age": 30,
457 "active": true,
458 "posts": ["post1", "post2"]
459 });
460
461 let skeleton = streamer.generate_skeleton(&json);
462 let expected = json!({
463 "name": null,
464 "age": 0,
465 "active": false,
466 "posts": []
467 });
468
469 assert_eq!(skeleton, expected);
470 }
471
472 #[test]
473 fn test_field_priority_calculation() {
474 let streamer = PriorityStreamer::new();
475 let path = JsonPath::root();
476
477 assert_eq!(
478 streamer.calculate_field_priority(&path, "id", &json!(123)),
479 Priority::CRITICAL
480 );
481
482 assert_eq!(
483 streamer.calculate_field_priority(&path, "name", &json!("John")),
484 Priority::HIGH
485 );
486
487 assert_eq!(
488 streamer.calculate_field_priority(&path, "reviews", &json!([])),
489 Priority::BACKGROUND
490 );
491 }
492
493 #[test]
494 fn test_streaming_plan_creation() {
495 let streamer = PriorityStreamer::new();
496 let json = json!({
497 "id": 1,
498 "name": "John",
499 "bio": "Software developer",
500 "reviews": ["Good", "Excellent"]
501 });
502
503 let plan = streamer.analyze(&json).unwrap();
504 assert!(!plan.is_complete());
505 assert!(plan.remaining_frames() > 0);
506 }
507
508 #[test]
516 fn test_round_trip_exact_repro_case() {
517 let streamer = PriorityStreamer::new();
518 let payload = json!({"items": [1, 2, 3]});
519
520 let result = round_trip(&streamer, &payload);
521
522 assert_eq!(result, payload);
523 assert_eq!(result["items"], json!([1, 2, 3]));
524 }
525
526 #[test]
527 fn test_round_trip_multi_entity_payload() {
528 let streamer = PriorityStreamer::new();
529 let payload = json!({
530 "users": [
531 {"id": 1, "name": "Alice"},
532 {"id": 2, "name": "Bob"}
533 ],
534 "metadata": {
535 "nested": {
536 "deep": [1, 2, 3, 4, 5]
537 }
538 }
539 });
540
541 let result = round_trip(&streamer, &payload);
542
543 assert_eq!(result, payload);
544 }
545
546 #[test]
547 fn test_round_trip_chunked_array_exceeds_max_patch_size() {
548 let config = StreamerConfig {
549 max_patch_size: 5,
550 ..StreamerConfig::default()
551 };
552 let streamer = PriorityStreamer::with_config(config);
553 let items: Vec<JsonValue> = (0..23).map(|i| json!(i)).collect();
554 let payload = json!({ "data": items });
555
556 let result = round_trip(&streamer, &payload);
557
558 assert_eq!(result, payload);
559 assert_eq!(result["data"].as_array().unwrap().len(), 23);
560 }
561
562 #[test]
563 fn test_round_trip_chunked_array_divergent_chunk_priority() {
564 let config = StreamerConfig {
573 max_patch_size: 60,
574 ..StreamerConfig::default()
575 };
576 let streamer = PriorityStreamer::with_config(config);
577 let items: Vec<JsonValue> = (0..130).map(|i| json!(i)).collect();
578 let payload = json!({ "items": items });
579
580 let result = round_trip(&streamer, &payload);
581
582 assert_eq!(result, payload);
583 }
584
585 #[test]
586 fn test_round_trip_array_nested_at_depth_three() {
587 let streamer = PriorityStreamer::new();
588 let payload = json!({
589 "level1": {
590 "level2": {
591 "level3": [1, 2, 3, 4]
592 }
593 }
594 });
595
596 let result = round_trip(&streamer, &payload);
597
598 assert_eq!(result, payload);
599 }
600
601 #[test]
602 fn test_round_trip_array_of_arrays() {
603 let streamer = PriorityStreamer::new();
604 let payload = json!({
605 "matrix": [[1, 2], [3, 4]]
606 });
607
608 let result = round_trip(&streamer, &payload);
609
610 assert_eq!(result, payload);
611 }
612
613 #[test]
614 fn test_round_trip_array_of_objects_with_nested_arrays() {
615 let streamer = PriorityStreamer::new();
616 let payload = json!({
617 "users": [
618 {"name": "Alice", "tags": ["admin", "active"]},
619 {"name": "Bob", "tags": ["guest"]}
620 ]
621 });
622
623 let result = round_trip(&streamer, &payload);
624
625 assert_eq!(result, payload);
626 }
627
628 #[test]
629 fn test_round_trip_empty_array_field() {
630 let streamer = PriorityStreamer::new();
631 let payload = json!({"items": []});
632
633 let result = round_trip(&streamer, &payload);
634
635 assert_eq!(result, payload);
636 }
637
638 #[test]
639 fn test_round_trip_top_level_bare_array() {
640 let streamer = PriorityStreamer::new();
641 let payload = json!([1, 2, 3]);
642
643 let result = round_trip(&streamer, &payload);
644
645 assert_eq!(result, payload);
646 }
647
648 #[test]
649 fn test_round_trip_mixed_payload_no_regression() {
650 let streamer = PriorityStreamer::new();
651 let payload = json!({
652 "id": 1,
653 "name": "widget",
654 "tags": ["a", "b", "c"],
655 "details": {
656 "color": "red",
657 "size": 10
658 }
659 });
660
661 let result = round_trip(&streamer, &payload);
662
663 assert_eq!(result, payload);
664 assert_eq!(result["tags"], json!(["a", "b", "c"]));
665 assert_eq!(result["details"], json!({"color": "red", "size": 10}));
666 }
667
668 #[test]
679 fn test_round_trip_same_path_priority_inversion() {
680 let streamer = PriorityStreamer::new();
686 let payload = json!({"history": [1, 2, 3]});
687
688 let result = round_trip(&streamer, &payload);
689
690 assert_eq!(result, payload);
691 }
692
693 #[test]
694 fn test_round_trip_parent_object_priority_inversion() {
695 let streamer = PriorityStreamer::new();
701 let payload = json!({"stats": {"values": [1, 2, 3]}});
702
703 let result = round_trip(&streamer, &payload);
704
705 assert_eq!(result, payload);
706 }
707
708 #[test]
709 fn test_round_trip_parent_object_priority_inversion_chunked() {
710 let streamer = PriorityStreamer::new();
715 let values: Vec<JsonValue> = (0..101).map(|i| json!(i)).collect();
716 let payload = json!({"stats": {"values": values}});
717
718 let result = round_trip(&streamer, &payload);
719
720 assert_eq!(result, payload);
721 assert_eq!(
722 result["stats"]["values"].as_array().unwrap().len(),
723 101,
724 "expected all 101 elements to survive the round trip"
725 );
726 }
727
728 #[test]
729 fn test_round_trip_unencodable_key_parent_wipe() {
730 let streamer = PriorityStreamer::new();
737 let payload = json!({"outer": {"weird.key": [1, 2, 3]}});
738
739 let result = round_trip(&streamer, &payload);
740
741 assert_eq!(result, payload);
742 }
743}