Skip to main content

neo_types/
map.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use std::vec::Vec;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9/// Neo N3 Map type
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12#[cfg_attr(
13    feature = "serde",
14    serde(bound(
15        serialize = "K: Serialize + Eq, V: Serialize",
16        deserialize = "K: Deserialize<'de> + Eq, V: Deserialize<'de>"
17    ))
18)]
19pub struct NeoMap<K, V> {
20    data: Vec<(K, V)>,
21}
22
23impl<K, V> NeoMap<K, V> {
24    pub fn new() -> Self {
25        Self { data: Vec::new() }
26    }
27
28    pub fn insert(&mut self, key: K, value: V) -> Option<V>
29    where
30        K: PartialEq,
31    {
32        for (k, v) in &mut self.data {
33            if *k == key {
34                return Some(core::mem::replace(v, value));
35            }
36        }
37        self.data.push((key, value));
38        None
39    }
40
41    /// Gets a reference to the value associated with the given key.
42    ///
43    /// # Performance
44    /// This operation is O(n) as it performs a linear search.
45    /// Consider using a HashMap for O(1) lookups if performance is critical.
46    pub fn get(&self, key: &K) -> Option<&V>
47    where
48        K: PartialEq,
49    {
50        self.data.iter().find(|(k, _)| k == key).map(|(_, v)| v)
51    }
52
53    /// Gets a mutable reference to the value associated with the given key.
54    ///
55    /// # Performance
56    /// This operation is O(n) as it performs a linear search.
57    pub fn get_mut(&mut self, key: &K) -> Option<&mut V>
58    where
59        K: PartialEq,
60    {
61        self.data.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v)
62    }
63
64    /// Removes the key-value pair associated with the given key.
65    ///
66    /// # Performance
67    /// This operation is O(n) due to the element removal.
68    ///
69    /// # Order stability
70    /// Uses `Vec::remove` (shift) so iteration order of remaining entries is
71    /// preserved (D7: `swap_remove` reordered entries, diverging from on-chain
72    /// NeoVM Map semantics and breaking deterministic serialization). Note that
73    /// on-chain NeoVM `Map` is sorted by key; this `NeoMap` is a linear map
74    /// (insertion-stable), not the on-chain sorted map.
75    pub fn remove(&mut self, key: &K) -> Option<V>
76    where
77        K: PartialEq,
78    {
79        self.data
80            .iter()
81            .position(|(k, _)| k == key)
82            .map(|i| self.data.remove(i).1)
83    }
84
85    pub fn len(&self) -> usize {
86        self.data.len()
87    }
88
89    pub fn is_empty(&self) -> bool {
90        self.data.is_empty()
91    }
92
93    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
94        self.data.iter().map(|(k, v)| (k, v))
95    }
96
97    /// Returns true if the map contains the given key.
98    pub fn contains_key(&self, key: &K) -> bool
99    where
100        K: PartialEq,
101    {
102        self.data.iter().any(|(k, _)| k == key)
103    }
104
105    /// Returns an iterator over the keys of the map.
106    pub fn keys(&self) -> impl Iterator<Item = &K> {
107        self.data.iter().map(|(k, _)| k)
108    }
109
110    /// Returns an iterator over the values of the map.
111    pub fn values(&self) -> impl Iterator<Item = &V> {
112        self.data.iter().map(|(_, v)| v)
113    }
114
115    /// Strict removal: only removes if both `key` AND `expected_value`
116    /// match. Returns `Ok(())` on success and `Err(Mismatch)` on value
117    /// mismatch or missing key. Mirrors the C# NeoVM `MAPREMOVE`
118    /// semantics (which FAULTs if value doesn't match), letting
119    /// contracts handle the bound explicitly.
120    ///
121    /// `Mismatch::Found` distinguishes "missing key" (the standard
122    /// `remove` would have returned `None`) from "wrong value" (a
123    /// potential optimistic-concurrency conflict).
124    pub fn remove_strict(&mut self, key: &K, expected_value: &V) -> Result<(), RemoveStrictError>
125    where
126        K: PartialEq,
127        V: PartialEq,
128    {
129        match self.data.iter().position(|(k, _)| k == key) {
130            Some(i) if &self.data[i].1 == expected_value => {
131                self.data.remove(i);
132                Ok(())
133            }
134            Some(_) => Err(RemoveStrictError::ValueMismatch),
135            None => Err(RemoveStrictError::Missing),
136        }
137    }
138}
139
140/// Error returned by [`NeoMap::remove_strict`].
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum RemoveStrictError {
143    /// The key was not present in the map.
144    Missing,
145    /// The key was present but the value did not match `expected_value`.
146    ValueMismatch,
147}
148
149impl core::fmt::Display for RemoveStrictError {
150    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151        match self {
152            RemoveStrictError::Missing => write!(f, "key not present in map"),
153            RemoveStrictError::ValueMismatch => {
154                write!(f, "value at key did not match expected value")
155            }
156        }
157    }
158}
159
160impl std::error::Error for RemoveStrictError {}
161
162impl<K, V> Default for NeoMap<K, V> {
163    fn default() -> Self {
164        Self::new()
165    }
166}
167
168impl<K, V> IntoIterator for NeoMap<K, V> {
169    type Item = (K, V);
170    type IntoIter = std::vec::IntoIter<(K, V)>;
171    fn into_iter(self) -> Self::IntoIter {
172        self.data.into_iter()
173    }
174}
175
176impl<'a, K, V> IntoIterator for &'a NeoMap<K, V> {
177    type Item = &'a (K, V);
178    type IntoIter = std::slice::Iter<'a, (K, V)>;
179    fn into_iter(self) -> std::slice::Iter<'a, (K, V)> {
180        self.data.iter()
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn remove_strict_succeeds_on_match() {
190        let mut m: NeoMap<u32, u32> = NeoMap::new();
191        m.insert(1, 100);
192        m.insert(2, 200);
193        assert!(m.remove_strict(&1, &100).is_ok());
194        assert!(!m.contains_key(&1));
195        assert!(m.contains_key(&2));
196    }
197
198    #[test]
199    fn remove_strict_returns_missing_on_absent_key() {
200        let mut m: NeoMap<u32, u32> = NeoMap::new();
201        m.insert(1, 100);
202        assert_eq!(m.remove_strict(&99, &100), Err(RemoveStrictError::Missing));
203    }
204
205    #[test]
206    fn remove_strict_returns_value_mismatch_on_wrong_value() {
207        let mut m: NeoMap<u32, u32> = NeoMap::new();
208        m.insert(1, 100);
209        assert_eq!(
210            m.remove_strict(&1, &999),
211            Err(RemoveStrictError::ValueMismatch)
212        );
213        // Key still present.
214        assert!(m.contains_key(&1));
215        assert_eq!(m.get(&1), Some(&100));
216    }
217}