blob: 26b34c3c826a9d514b94f76f9a09b54ddb3c7ff7 [file] [log] [blame]
// 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());
}
}