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;
9
10///
11/// MapValueError
12///
13/// Reports invariant violations found while constructing or normalizing
14/// `Value::Map` entries. The error carries normalized entry positions where
15/// possible so callers can diagnose duplicate-key collisions deterministically.
16///
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum MapValueError {
20    EmptyKey {
21        index: usize,
22    },
23    NonScalarKey {
24        index: usize,
25        key: Value,
26    },
27    NonScalarValue {
28        index: usize,
29        value: Value,
30    },
31    DuplicateKey {
32        left_index: usize,
33        right_index: usize,
34    },
35}
36
37impl std::fmt::Display for MapValueError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::EmptyKey { index } => write!(f, "map key at index {index} must be non-null"),
41            Self::NonScalarKey { index, key } => {
42                write!(f, "map key at index {index} is not scalar: {key:?}")
43            }
44            Self::NonScalarValue { index, value } => {
45                write!(
46                    f,
47                    "map value at index {index} is not scalar/ref-like: {value:?}"
48                )
49            }
50            Self::DuplicateKey {
51                left_index,
52                right_index,
53            } => write!(
54                f,
55                "map contains duplicate keys at normalized positions {left_index} and {right_index}"
56            ),
57        }
58    }
59}
60
61impl std::error::Error for MapValueError {}
62
63///
64/// SchemaInvariantError
65///
66/// Wraps schema/runtime materialization invariant failures that surface through
67/// generic conversion traits. This keeps map-specific validation errors intact
68/// while preserving the existing `TryFrom` error boundary for `Value`.
69///
70
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub enum SchemaInvariantError {
73    InvalidMapValue(MapValueError),
74}
75
76impl std::fmt::Display for SchemaInvariantError {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        match self {
79            Self::InvalidMapValue(err) => write!(f, "{err}"),
80        }
81    }
82}
83
84impl std::error::Error for SchemaInvariantError {}
85
86impl From<MapValueError> for SchemaInvariantError {
87    fn from(value: MapValueError) -> Self {
88        Self::InvalidMapValue(value)
89    }
90}
91
92/// Validate map entry invariants without changing order.
93pub fn validate_map_entries(entries: &[(Value, Value)]) -> Result<(), MapValueError> {
94    for (index, (key, _value)) in entries.iter().enumerate() {
95        if matches!(key, Value::Null) {
96            return Err(MapValueError::EmptyKey { index });
97        }
98        if !key.is_scalar() {
99            return Err(MapValueError::NonScalarKey {
100                index,
101                key: key.clone(),
102            });
103        }
104    }
105
106    Ok(())
107}
108
109// Compare two map entries by canonical key order.
110pub(crate) fn compare_map_entry_keys(left: &(Value, Value), right: &(Value, Value)) -> Ordering {
111    Value::canonical_cmp_key(&left.0, &right.0)
112}
113
114// Sort map entries in canonical key order without changing ownership.
115pub(crate) fn sort_map_entries_in_place(entries: &mut [(Value, Value)]) {
116    entries.sort_by(compare_map_entry_keys);
117}
118
119// Return `true` when map entries are already in strict canonical order and
120// therefore contain no duplicate canonical keys.
121pub(crate) fn map_entries_are_strictly_canonical(entries: &[(Value, Value)]) -> bool {
122    entries.windows(2).all(|pair| {
123        let [left, right] = pair else {
124            return true;
125        };
126
127        compare_map_entry_keys(left, right) == Ordering::Less
128    })
129}
130
131/// Normalize map entries into canonical deterministic order.
132pub fn normalize_map_entries(
133    mut entries: Vec<(Value, Value)>,
134) -> Result<Vec<(Value, Value)>, MapValueError> {
135    validate_map_entries(&entries)?;
136    sort_map_entries_in_place(entries.as_mut_slice());
137
138    for i in 1..entries.len() {
139        let (left_key, _) = &entries[i - 1];
140        let (right_key, _) = &entries[i];
141        if Value::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
142            return Err(MapValueError::DuplicateKey {
143                left_index: i - 1,
144                right_index: i,
145            });
146        }
147    }
148
149    Ok(entries)
150}
151
152impl Value {
153    /// Validate map entry invariants without changing order.
154    pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
155        validate_map_entries(entries)
156    }
157
158    // Sort map entries in canonical key order without changing ownership.
159    pub(crate) fn sort_map_entries_in_place(entries: &mut [(Self, Self)]) {
160        sort_map_entries_in_place(entries);
161    }
162
163    // Return `true` when map entries are already in strict canonical order and
164    // therefore contain no duplicate canonical keys.
165    pub(crate) fn map_entries_are_strictly_canonical(entries: &[(Self, Self)]) -> bool {
166        map_entries_are_strictly_canonical(entries)
167    }
168
169    /// Normalize map entries into canonical deterministic order.
170    pub fn normalize_map_entries(
171        entries: Vec<(Self, Self)>,
172    ) -> Result<Vec<(Self, Self)>, MapValueError> {
173        normalize_map_entries(entries)
174    }
175}