use crate::domain::{
DomainResult,
value_objects::{JsonPath, Priority},
};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PriorityService {
field_rules: HashMap<String, Priority>,
path_rules: HashMap<String, Priority>,
type_rules: HashMap<String, Priority>,
default_priority: Priority,
}
impl PriorityService {
pub fn new() -> Self {
let mut service = Self {
field_rules: HashMap::new(),
path_rules: HashMap::new(),
type_rules: HashMap::new(),
default_priority: Priority::MEDIUM,
};
service.add_default_rules();
service
}
pub fn with_default_priority(default_priority: Priority) -> Self {
let mut service = Self::new();
service.default_priority = default_priority;
service
}
pub fn add_field_rule(&mut self, field_name: String, priority: Priority) {
self.field_rules.insert(field_name, priority);
}
pub fn add_path_rule(&mut self, path_pattern: String, priority: Priority) {
self.path_rules.insert(path_pattern, priority);
}
pub fn add_type_rule(&mut self, type_name: String, priority: Priority) {
self.type_rules.insert(type_name, priority);
}
pub fn calculate_priority(&self, path: &JsonPath, value: &JsonValue) -> Priority {
if let Some(&priority) = self.path_rules.get(path.as_str()) {
return priority;
}
for (pattern, &priority) in &self.path_rules {
if self.path_matches_pattern(path.as_str(), pattern) {
return priority;
}
}
if let Some(field_name) = self.extract_field_name(path)
&& let Some(&priority) = self.field_rules.get(&field_name)
{
return priority;
}
let type_name = self.get_value_type_name(value);
if let Some(&priority) = self.type_rules.get(type_name) {
return priority;
}
self.apply_heuristic_priority(path, value)
}
pub fn calculate_priorities(
&self,
data: &JsonValue,
) -> DomainResult<HashMap<JsonPath, Priority>> {
let mut priorities = HashMap::new();
self.calculate_priorities_recursive(data, &JsonPath::root(), &mut priorities)?;
Ok(priorities)
}
pub fn optimize_rules(&mut self, usage_stats: &UsageStatistics) {
for (field, access_count) in &usage_stats.field_access_counts {
if *access_count > usage_stats.average_access_count * 2 {
let current = self
.field_rules
.get(field)
.copied()
.unwrap_or(self.default_priority);
let optimized = current.increase_by(10);
self.field_rules.insert(field.clone(), optimized);
}
}
for (field, access_count) in &usage_stats.field_access_counts {
if *access_count < usage_stats.average_access_count / 3 {
let current = self
.field_rules
.get(field)
.copied()
.unwrap_or(self.default_priority);
let optimized = current.decrease_by(5);
self.field_rules.insert(field.clone(), optimized);
}
}
}
pub fn get_rules_summary(&self) -> PriorityRulesSummary {
PriorityRulesSummary {
field_rule_count: self.field_rules.len(),
path_rule_count: self.path_rules.len(),
type_rule_count: self.type_rules.len(),
default_priority: self.default_priority,
}
}
fn add_default_rules(&mut self) {
self.add_field_rule("id".to_string(), Priority::CRITICAL);
self.add_field_rule("uuid".to_string(), Priority::CRITICAL);
self.add_field_rule("status".to_string(), Priority::CRITICAL);
self.add_field_rule("state".to_string(), Priority::CRITICAL);
self.add_field_rule("error".to_string(), Priority::CRITICAL);
self.add_field_rule("name".to_string(), Priority::HIGH);
self.add_field_rule("title".to_string(), Priority::HIGH);
self.add_field_rule("label".to_string(), Priority::HIGH);
self.add_field_rule("description".to_string(), Priority::HIGH);
self.add_field_rule("message".to_string(), Priority::HIGH);
self.add_field_rule("content".to_string(), Priority::MEDIUM);
self.add_field_rule("body".to_string(), Priority::MEDIUM);
self.add_field_rule("value".to_string(), Priority::MEDIUM);
self.add_field_rule("data".to_string(), Priority::MEDIUM);
self.add_field_rule("created_at".to_string(), Priority::LOW);
self.add_field_rule("updated_at".to_string(), Priority::LOW);
self.add_field_rule("version".to_string(), Priority::LOW);
self.add_field_rule("metadata".to_string(), Priority::LOW);
self.add_field_rule("analytics".to_string(), Priority::BACKGROUND);
self.add_field_rule("debug".to_string(), Priority::BACKGROUND);
self.add_field_rule("trace".to_string(), Priority::BACKGROUND);
self.add_field_rule("logs".to_string(), Priority::BACKGROUND);
self.add_path_rule("$.error".to_string(), Priority::CRITICAL);
self.add_path_rule("$.*.id".to_string(), Priority::CRITICAL);
self.add_path_rule("$.users[*].id".to_string(), Priority::CRITICAL);
self.add_path_rule("$.*.analytics".to_string(), Priority::BACKGROUND);
self.add_type_rule("array_large".to_string(), Priority::LOW); self.add_type_rule("string_long".to_string(), Priority::LOW); self.add_type_rule("object_deep".to_string(), Priority::LOW); }
fn path_matches_pattern(&self, path: &str, pattern: &str) -> bool {
if pattern.contains('*') {
let parts: Vec<&str> = pattern.split('*').collect();
if parts.is_empty() {
return true;
}
let mut pos = 0;
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
if i == 0 {
if !path.starts_with(part) {
return false;
}
pos = part.len();
} else if i == parts.len() - 1 {
if !path[pos..].ends_with(part) {
return false;
}
} else {
if let Some(found) = path[pos..].find(part) {
pos += found + part.len();
} else {
return false;
}
}
}
true
} else {
path == pattern
}
}
fn extract_field_name(&self, path: &JsonPath) -> Option<String> {
if let Some(segment) = path.last_segment() {
match segment {
crate::domain::value_objects::PathSegment::Key(key) => Some(key),
_ => None,
}
} else {
None
}
}
fn get_value_type_name(&self, value: &JsonValue) -> &'static str {
match value {
JsonValue::Null => "null",
JsonValue::Bool(_) => "boolean",
JsonValue::Number(_) => "number",
JsonValue::String(s) => {
if s.len() > 1000 {
"string_long"
} else {
"string"
}
}
JsonValue::Array(arr) => {
if arr.len() > 100 {
"array_large"
} else {
"array"
}
}
JsonValue::Object(obj) => {
if obj.len() > 20 {
"object_large"
} else {
"object"
}
}
}
}
fn apply_heuristic_priority(&self, path: &JsonPath, value: &JsonValue) -> Priority {
let mut priority = self.default_priority;
let depth = path.depth();
if depth == 1 {
priority = priority.increase_by(20);
} else if depth == 2 {
priority = priority.increase_by(10);
} else if depth > 5 {
priority = priority.decrease_by(10);
}
match value {
JsonValue::String(s) if s.len() < 50 => {
priority = priority.increase_by(5);
}
JsonValue::Array(arr) if arr.len() > 10 => {
priority = priority.decrease_by(15);
}
JsonValue::Object(obj) if obj.len() > 10 => {
priority = priority.decrease_by(10);
}
_ => {}
}
priority
}
fn calculate_priorities_recursive(
&self,
value: &JsonValue,
current_path: &JsonPath,
priorities: &mut HashMap<JsonPath, Priority>,
) -> DomainResult<()> {
let priority = self.calculate_priority(current_path, value);
priorities.insert(current_path.clone(), priority);
match value {
JsonValue::Object(obj) => {
for (key, child_value) in obj.iter() {
let child_path = current_path.append_key(key)?;
self.calculate_priorities_recursive(child_value, &child_path, priorities)?;
}
}
JsonValue::Array(arr) => {
for (index, child_value) in arr.iter().enumerate() {
let child_path = current_path.append_index(index);
self.calculate_priorities_recursive(child_value, &child_path, priorities)?;
}
}
_ => {
}
}
Ok(())
}
}
impl Default for PriorityService {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct UsageStatistics {
pub field_access_counts: HashMap<String, u64>,
pub total_accesses: u64,
pub average_access_count: u64,
}
#[derive(Debug, Clone)]
pub struct PriorityRulesSummary {
pub field_rule_count: usize,
pub path_rule_count: usize,
pub type_rule_count: usize,
pub default_priority: Priority,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_priority_rules() {
let service = PriorityService::new();
let path = JsonPath::new("$.id").unwrap();
let value = JsonValue::String("123".to_string());
assert_eq!(
service.calculate_priority(&path, &value),
Priority::CRITICAL
);
}
#[test]
fn test_path_pattern_matching() {
let service = PriorityService::new();
assert!(service.path_matches_pattern("$.users[0].id", "$.*.id"));
assert!(service.path_matches_pattern("$.posts[1].id", "$.*.id"));
assert!(!service.path_matches_pattern("$.users.name", "$.*.id"));
}
#[test]
fn test_custom_rules() {
let mut service = PriorityService::new();
service.add_field_rule("custom_field".to_string(), Priority::HIGH);
let path = JsonPath::new("$.custom_field").unwrap();
let value = JsonValue::String("test".to_string());
assert_eq!(service.calculate_priority(&path, &value), Priority::HIGH);
}
#[test]
fn test_heuristic_priority() {
let service = PriorityService::new();
let shallow = JsonPath::new("$.name").unwrap();
let deep = JsonPath::new("$.user.profile.settings.theme.color").unwrap();
let value = JsonValue::String("test".to_string());
assert!(
service.calculate_priority(&shallow, &value)
> service.calculate_priority(&deep, &value)
);
}
#[test]
fn test_calculate_all_priorities() {
let service = PriorityService::new();
let data =
serde_json::json!({"id": 123, "name": "Test", "details": {"description": "A test"}});
let priorities = service.calculate_priorities(&data).unwrap();
assert!(!priorities.is_empty());
let id_path = JsonPath::new("$.id").unwrap();
assert_eq!(priorities[&id_path], Priority::CRITICAL);
}
#[test]
fn test_with_default_priority_constructor() {
let service = PriorityService::with_default_priority(Priority::LOW);
let summary = service.get_rules_summary();
assert_eq!(summary.default_priority, Priority::LOW);
}
#[test]
fn test_default_trait() {
let service = PriorityService::default();
let summary = service.get_rules_summary();
assert!(summary.field_rule_count > 0);
assert_eq!(summary.default_priority, Priority::MEDIUM);
}
#[test]
fn test_add_path_rule() {
let mut service = PriorityService::new();
service.add_path_rule("$.custom.path".to_string(), Priority::CRITICAL);
let path = JsonPath::new("$.custom.path").unwrap();
let value = JsonValue::String("test".to_string());
assert_eq!(
service.calculate_priority(&path, &value),
Priority::CRITICAL
);
}
#[test]
fn test_add_type_rule() {
let mut service = PriorityService::new();
service.add_type_rule("boolean".to_string(), Priority::CRITICAL);
let path = JsonPath::new("$.some_bool").unwrap();
let value = JsonValue::Bool(true);
assert_eq!(
service.calculate_priority(&path, &value),
Priority::CRITICAL
);
}
#[test]
fn test_get_rules_summary() {
let service = PriorityService::new();
let summary = service.get_rules_summary();
assert_eq!(summary.field_rule_count, 22);
assert_eq!(summary.path_rule_count, 4);
assert_eq!(summary.type_rule_count, 3);
assert_eq!(summary.default_priority, Priority::MEDIUM);
}
#[test]
fn test_optimize_rules_increases_priority() {
let mut service = PriorityService::new();
let stats = UsageStatistics {
field_access_counts: {
let mut m = HashMap::new();
m.insert("hot_field".to_string(), 100);
m
},
total_accesses: 100,
average_access_count: 30,
};
service.optimize_rules(&stats);
let summary = service.get_rules_summary();
assert!(summary.field_rule_count > 22);
}
#[test]
fn test_optimize_rules_decreases_priority() {
let mut service = PriorityService::new();
let stats = UsageStatistics {
field_access_counts: {
let mut m = HashMap::new();
m.insert("cold_field".to_string(), 5);
m
},
total_accesses: 100,
average_access_count: 30,
};
service.optimize_rules(&stats);
let summary = service.get_rules_summary();
assert!(summary.field_rule_count > 22);
}
#[test]
fn test_optimize_rules_with_existing_field() {
let mut service = PriorityService::new();
let stats = UsageStatistics {
field_access_counts: {
let mut m = HashMap::new();
m.insert("id".to_string(), 100);
m
},
total_accesses: 100,
average_access_count: 30,
};
service.optimize_rules(&stats);
let path = JsonPath::new("$.id").unwrap();
let value = JsonValue::String("123".to_string());
assert_eq!(
service.calculate_priority(&path, &value),
Priority::CRITICAL.increase_by(10)
);
}
#[test]
fn test_type_based_priority_large_array() {
let service = PriorityService::new();
let large_array: Vec<JsonValue> = (0..101).map(|i| JsonValue::Number(i.into())).collect();
let path = JsonPath::new("$.items").unwrap();
let value = JsonValue::Array(large_array);
assert_eq!(service.calculate_priority(&path, &value), Priority::LOW);
}
#[test]
fn test_type_based_priority_long_string() {
let mut service = PriorityService::new();
service.field_rules.clear();
let long_string = "x".repeat(1001);
let path = JsonPath::new("$.long_content").unwrap();
let value = JsonValue::String(long_string);
assert_eq!(service.calculate_priority(&path, &value), Priority::LOW);
}
#[test]
fn test_type_based_priority_null() {
let service = PriorityService::new();
let path = JsonPath::new("$.null_value").unwrap();
let value = JsonValue::Null;
assert!(service.calculate_priority(&path, &value) > Priority::MEDIUM);
}
#[test]
fn test_type_based_priority_number() {
let service = PriorityService::new();
let path = JsonPath::new("$.count").unwrap();
let value = JsonValue::Number(serde_json::Number::from(42));
assert!(service.calculate_priority(&path, &value) >= Priority::MEDIUM);
}
#[test]
fn test_heuristic_large_array_penalty() {
let mut service = PriorityService::new();
service.field_rules.clear();
service.path_rules.clear();
service.type_rules.clear();
let arr: Vec<JsonValue> = (0..15).map(|i| JsonValue::Number(i.into())).collect();
let path = JsonPath::new("$.arr").unwrap();
let value = JsonValue::Array(arr);
let small_arr: Vec<JsonValue> = (0..5).map(|i| JsonValue::Number(i.into())).collect();
let small_value = JsonValue::Array(small_arr);
assert!(
service.calculate_priority(&path, &value)
< service.calculate_priority(&path, &small_value)
);
}
#[test]
fn test_heuristic_complex_object_penalty() {
let mut service = PriorityService::new();
service.field_rules.clear();
service.path_rules.clear();
service.type_rules.clear();
let mut obj = serde_json::Map::new();
for i in 0..15 {
obj.insert(format!("f{}", i), JsonValue::Number(i.into()));
}
let path = JsonPath::new("$.obj").unwrap();
let value = JsonValue::Object(obj);
let mut small_obj = serde_json::Map::new();
for i in 0..5 {
small_obj.insert(format!("f{}", i), JsonValue::Number(i.into()));
}
let small_value = JsonValue::Object(small_obj);
assert!(
service.calculate_priority(&path, &value)
< service.calculate_priority(&path, &small_value)
);
}
#[test]
fn test_heuristic_depth_2() {
let mut service = PriorityService::new();
service.field_rules.clear();
service.path_rules.clear();
service.type_rules.clear();
let path_depth_2 = JsonPath::new("$.a.b").unwrap();
let value = JsonValue::Number(serde_json::Number::from(1));
let path_depth_1 = JsonPath::new("$.a").unwrap();
assert!(
service.calculate_priority(&path_depth_1, &value)
> service.calculate_priority(&path_depth_2, &value)
);
}
#[test]
fn test_path_pattern_exact_match() {
let service = PriorityService::new();
assert!(service.path_matches_pattern("$.error", "$.error"));
assert!(!service.path_matches_pattern("$.errors", "$.error"));
}
#[test]
fn test_path_pattern_wildcard_at_end() {
let service = PriorityService::new();
assert!(service.path_matches_pattern("$.users", "$.users*"));
assert!(service.path_matches_pattern("$.users[0]", "$.users*"));
assert!(!service.path_matches_pattern("$.posts", "$.users*"));
}
#[test]
fn test_path_pattern_wildcard_in_middle() {
let service = PriorityService::new();
assert!(service.path_matches_pattern("$.ab", "$.a*b"));
assert!(service.path_matches_pattern("$.axyzb", "$.a*b"));
assert!(!service.path_matches_pattern("$.axyzc", "$.a*b"));
}
#[test]
fn test_path_pattern_wildcard_not_matching_start() {
let service = PriorityService::new();
assert!(!service.path_matches_pattern("$.posts.id", "$.users*.id"));
}
#[test]
fn test_extract_field_name_with_array_index() {
let service = PriorityService::new();
let path = JsonPath::new("$.items[0]").unwrap();
let value = JsonValue::Number(serde_json::Number::from(1));
assert!(service.calculate_priority(&path, &value) >= Priority::LOW);
}
#[test]
fn test_extract_field_name_root_path() {
let service = PriorityService::new();
let path = JsonPath::root();
let value = serde_json::json!({"id": 1});
assert!(service.calculate_priority(&path, &value) >= Priority::LOW);
}
#[test]
fn test_calculate_priorities_with_array() {
let service = PriorityService::new();
let data = serde_json::json!([1, 2, 3]);
let priorities = service.calculate_priorities(&data).unwrap();
assert_eq!(priorities.len(), 4);
}
#[test]
fn test_calculate_priorities_nested_array() {
let service = PriorityService::new();
let data = serde_json::json!({"items": [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]});
let priorities = service.calculate_priorities(&data).unwrap();
assert!(priorities.len() >= 8);
let id_path = JsonPath::new("$.items[0].id").unwrap();
assert_eq!(priorities[&id_path], Priority::CRITICAL);
}
}