| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{DuplicateKeyError, InsertUniqueExt, MapEntry}; |
| use anyhow::{anyhow, Result}; |
| use serde::Serialize; |
| use std::collections::BTreeMap; |
| |
| /// A key for a NamedMap |
| pub trait Key: Ord + std::fmt::Display + std::fmt::Debug {} |
| |
| impl Key for &str {} |
| impl Key for String {} |
| |
| /// A named set of things, which are mapped by a String K. |
| #[derive(Clone, Debug, PartialEq, Serialize)] |
| pub struct NamedMap<K: Key, T> { |
| /// The name of the Map. |
| pub name: String, |
| |
| /// The entries in the map. |
| pub entries: BTreeMap<K, T>, |
| } |
| |
| impl<K: Key, T> NamedMap<K, T> |
| where |
| T: std::fmt::Debug + std::cmp::PartialEq, |
| { |
| /// Create a new, named, map. |
| pub fn new(name: &str) -> Self { |
| Self { name: name.to_owned(), entries: BTreeMap::new() } |
| } |
| |
| /// Insert `value` into the map returning an error if the key exists and the value is |
| /// different. |
| /// |
| /// If the key is unique, return Ok(()) |
| /// If the key is a duplicate, but the value is the same, return Err((true, anyhow::Error)) |
| /// If the key is a duplicate, and the value is different, return Err((false, anyhow::Error)) |
| fn try_insert_check_for_duplicate( |
| &mut self, |
| name: K, |
| value: T, |
| ) -> Result<(), (bool, anyhow::Error)> { |
| let result = self.entries.try_insert_unique(MapEntry(name, value)).map_err(|e| { |
| // Indicate whether the error is due to a duplicate by adding a boolean to the return. |
| let duplicate = e.previous_value() == e.new_value(); |
| ( |
| duplicate, |
| format!( |
| "key: '{}'\n existing value: {:#?}\n new value: {:#?}", |
| e.key(), |
| e.previous_value(), |
| e.new_value() |
| ), |
| ) |
| }); |
| // The error is mapped a second time to separate the borrow of entries |
| // from the borrow of name. |
| result.map_err(|(d, e)| (d, anyhow!("duplicate entry in {}:\n {}", self.name, e))) |
| } |
| |
| /// Insert `value` into the map ensuring that `name` is unique. |
| pub fn try_insert_unique(&mut self, name: K, value: T) -> Result<()> { |
| // Ignore the returned 'duplicate' boolean. If there is an error, return it. |
| self.try_insert_check_for_duplicate(name, value).map_err(|(_, e)| e) |
| } |
| |
| /// Insert `value` into the map ensuring that if `name` is found that the values are identical. |
| pub fn try_insert_unique_ignore_duplicates(&mut self, name: K, value: T) -> Result<()> { |
| match self.try_insert_check_for_duplicate(name, value) { |
| // If the key is unique or the value is a duplicate, return Ok(()) |
| Ok(_) | Err((true, _)) => Ok(()), |
| // Otherwise, forward the error. |
| Err((false, e)) => Err(e), |
| } |
| } |
| } |
| |
| impl<K: Key, T> std::ops::Deref for NamedMap<K, T> { |
| type Target = BTreeMap<K, T>; |
| |
| fn deref(&self) -> &Self::Target { |
| &self.entries |
| } |
| } |
| |
| impl<K: Key, T> std::ops::DerefMut for NamedMap<K, T> { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.entries |
| } |
| } |
| |
| impl<K: Key, T> IntoIterator for NamedMap<K, T> { |
| type Item = (K, T); |
| type IntoIter = std::collections::btree_map::IntoIter<K, T>; |
| fn into_iter(self) -> Self::IntoIter { |
| self.entries.into_iter() |
| } |
| } |
| |
| impl<'a, K: Key, T> IntoIterator for &'a NamedMap<K, T> { |
| type Item = (&'a K, &'a T); |
| type IntoIter = std::collections::btree_map::Iter<'a, K, T>; |
| fn into_iter(self) -> Self::IntoIter { |
| (&self.entries).into_iter() |
| } |
| } |
| |
| impl<K: Key, T> FromIterator<(K, T)> for NamedMap<K, T> { |
| fn from_iter<I: IntoIterator<Item = (K, T)>>(iter: I) -> Self { |
| Self { name: "from iterator".into(), entries: BTreeMap::from_iter(iter) } |
| } |
| } |
| |
| impl<K: Key, T, const N: usize> From<[(K, T); N]> for NamedMap<K, T> { |
| fn from(arr: [(K, T); N]) -> Self { |
| Self { name: "from".into(), entries: BTreeMap::from(arr) } |
| } |
| } |
| |
| impl<K: Key, T> From<NamedMap<K, T>> for BTreeMap<K, T> { |
| fn from(map: NamedMap<K, T>) -> Self { |
| map.entries |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_try_insert_unique() { |
| let mut map = NamedMap::new("test"); |
| map.try_insert_unique("a".to_string(), "alpha").unwrap(); |
| map.try_insert_unique("b".to_string(), "beta").unwrap(); |
| assert!(map.try_insert_unique("a".to_string(), "alpha").is_err()); |
| } |
| |
| #[test] |
| fn test_try_insert_unique_ignore_duplicates() { |
| let mut map = NamedMap::new("test"); |
| map.try_insert_unique("a".to_string(), "alpha").unwrap(); |
| map.try_insert_unique("b".to_string(), "beta").unwrap(); |
| map.try_insert_unique_ignore_duplicates("a".into(), "alpha").unwrap(); |
| assert!(map.try_insert_unique_ignore_duplicates("a".to_string(), "beta").is_err()); |
| } |
| } |