Skip to main content

icydb_core/value/
map.rs

1//! Module: value::map
2//!
3//! Responsibility: canonical map normalization and validation for `Value::Map`.
4//! Does not own: the `Value` enum shape or storage-level map encoding.
5//! Boundary: deterministic map construction helpers shared by runtime surfaces.
6
7use crate::value::Value;
8use std::cmp::Ordering;
9use std::fmt;
10
11///
12/// MapValueError
13///
14/// Reports invariant violations found while constructing or normalizing
15/// `Value::Map` entries. The error carries normalized entry positions where
16/// possible so callers can diagnose duplicate-key collisions deterministically.
17///
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20pub enum MapValueError {
21    EmptyKey {
22        index: usize,
23    },
24    NonScalarKey {
25        index: usize,
26        key: Value,
27    },
28    NonScalarValue {
29        index: usize,
30        value: Value,
31    },
32    DuplicateKey {
33        left_index: usize,
34        right_index: usize,
35    },
36}
37
38impl fmt::Display for MapValueError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str("map value error")
41    }
42}
43
44impl std::error::Error for MapValueError {}
45
46///
47/// SchemaInvariantError
48///
49/// Wraps schema/runtime materialization invariant failures that surface through
50/// generic conversion traits. This keeps map-specific validation errors intact
51/// while preserving the existing `TryFrom` error boundary for `Value`.
52///
53
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub enum SchemaInvariantError {
56    InvalidMapValue(MapValueError),
57}
58
59impl fmt::Display for SchemaInvariantError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str("schema invariant error")
62    }
63}
64
65impl std::error::Error for SchemaInvariantError {}
66
67impl From<MapValueError> for SchemaInvariantError {
68    fn from(value: MapValueError) -> Self {
69        Self::InvalidMapValue(value)
70    }
71}
72
73/// Validate map entry invariants without changing order.
74fn validate_map_entries(entries: &[(Value, Value)]) -> Result<(), MapValueError> {
75    for (index, (key, _value)) in entries.iter().enumerate() {
76        if matches!(key, Value::Null) {
77            return Err(MapValueError::EmptyKey { index });
78        }
79        if !key.is_scalar() {
80            return Err(MapValueError::NonScalarKey {
81                index,
82                key: key.clone(),
83            });
84        }
85    }
86
87    Ok(())
88}
89
90// Compare two map entries by canonical key order.
91fn compare_map_entry_keys(left: &(Value, Value), right: &(Value, Value)) -> Ordering {
92    Value::canonical_cmp_key(&left.0, &right.0)
93}
94
95// Sort map entries in canonical key order without changing ownership.
96fn sort_map_entries_in_place(entries: &mut [(Value, Value)]) {
97    entries.sort_by(compare_map_entry_keys);
98}
99
100/// Normalize map entries into canonical deterministic order.
101pub(super) fn normalize_map_entries(
102    mut entries: Vec<(Value, Value)>,
103) -> Result<Vec<(Value, Value)>, MapValueError> {
104    validate_map_entries(&entries)?;
105    sort_map_entries_in_place(entries.as_mut_slice());
106
107    for i in 1..entries.len() {
108        let (left_key, _) = &entries[i - 1];
109        let (right_key, _) = &entries[i];
110        if Value::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
111            return Err(MapValueError::DuplicateKey {
112                left_index: i - 1,
113                right_index: i,
114            });
115        }
116    }
117
118    Ok(entries)
119}
120
121impl Value {
122    /// Validate map entry invariants without changing order.
123    pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
124        validate_map_entries(entries)
125    }
126
127    /// Normalize map entries into canonical deterministic order.
128    pub fn normalize_map_entries(
129        entries: Vec<(Self, Self)>,
130    ) -> Result<Vec<(Self, Self)>, MapValueError> {
131        normalize_map_entries(entries)
132    }
133}