diff --git a/Cargo.lock b/Cargo.lock index 9fac2cb..284b98d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,10 @@ dependencies = [ [[package]] name = "identified_vec" -version = "0.1.2" +version = "0.1.3" dependencies = [ "anyerror", + "identified_vec", "maplit", "rand", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5bd8f89..cb6aaed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identified_vec" -version = "0.1.2" +version = "0.1.3" edition = "2021" authors = ["Alexander Cyon "] description = "Like HashSet but retaining INSERTION order and without `Hash` requirement on the Element type." @@ -10,16 +10,17 @@ repository = "https://github.com/Sajjon/identified_vec" keywords = ["identifiable", "vec", "orderset", "set", "hashset"] categories = ["data-structures"] +[features] +serde = ["dep:serde"] +id_prim = [] + [dependencies] anyerror = "0.1.12" serde = { version = "1.0.193", optional = true } thiserror = "1.0.50" -[features] -default = ["serde"] -serde = ["dep:serde"] - [dev-dependencies] +identified_vec = { path = ".", features = ["id_prim", "serde"] } serde = "1.0.193" serde_json = "1.0.108" rand = "0.8.5" diff --git a/README.md b/README.md index fa84f2b..8bc8f18 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # `identified_vec` -[![codecov](https://codecov.io/github/Sajjon/identified_vec/graph/badge.svg?token=Em6TayrP8j)](https://codecov.io/github/Sajjon/identified_vec) +[![Code Coverage](https://codecov.io/github/Sajjon/identified_vec/graph/badge.svg?token=Em6TayrP8j)](https://codecov.io/github/Sajjon/identified_vec) +[![Crates.io](https://img.shields.io/crates/v/identified_vec.svg)](https://crates.io/crates/identified_vec) +[![Documentation](https://docs.rs/identified_vec/badge.svg)](https://docs.rs/identified_vec) +[![Rust](https://img.shields.io/badge/rust-1.73.0%2B-blue.svg?maxAge=3600)](https://github.com/Sajjon/identified_vec) A collection of unique identifiable elements which retains **insertion** order, inspired by [Pointfree's Swift Identified Collections](https://github.com/pointfreeco/swift-identified-collections). @@ -10,11 +13,11 @@ Similar to the standard `Vec`, the `IdentifiedVec` maintain their elements in a You can create an identified vec with any element type that implements the `Identifiable` trait. +# Example + ```rust extern crate identified_vec; -use identified_vec::identified_vec::IdentifiedVec; -use identified_vec::identifiable::Identifiable; -use identified_vec::identified_vec_of::IdentifiedVecOf; +use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; use std::cell::RefCell; #[derive(Eq, PartialEq, Clone, Debug)] @@ -34,14 +37,22 @@ impl User { *self.name.borrow() } } +``` + +## Identifiable +```rust impl Identifiable for User { type ID = &'static str; fn id(&self) -> Self::ID { self.id } } +``` + +## `from_iter` +```rust let mut users = IdentifiedVecOf::::from_iter([ User::new("u_42", "Satoshi Nakamoto"), User::new("u_1337", "Leia Skywalker"), @@ -56,7 +67,11 @@ assert_eq!( users.get_at_index(1).map(|u| u.name()), Some("Leia Skywalker") ); +``` + +## `append` & `elements()` +```rust users.append(User::new("u_237", "Alan Turing")); assert_eq!( users.elements(), @@ -81,7 +96,11 @@ assert_eq!( .iter() .collect::>() ); +``` + +## `update_or_insert` +```rust // Element with same ID replaces existing if an `update_*` method is used: // e.g. `update_or_insert`: users.update_or_insert(User::new("u_42", "Tom Mervolo Dolder"), 0); @@ -95,7 +114,11 @@ assert_eq!( .iter() .collect::>() ); +``` + +## `update_or_append` +```rust // or `update_or_append` users.update_or_append(User::new("u_237", "Marie Curie")); assert_eq!( @@ -108,7 +131,11 @@ assert_eq!( .iter() .collect::>() ); +``` + +## `get_mut` +```rust // or mutate with `get_mut(id)` *users.get_mut(&"u_1337").unwrap().name.get_mut() = "Yoda"; assert_eq!( @@ -126,10 +153,6 @@ assert_eq!( Or you can provide a closure that describes an element's identity: ```rust -use identified_vec::identified_vec::IdentifiedVec; -use identified_vec::identifiable::Identifiable; -use identified_vec::identified_vec_of::IdentifiedVecOf; - let numbers = IdentifiedVec::::new_identifying_element(|e| *e); ``` @@ -137,16 +160,17 @@ let numbers = IdentifiedVec::::new_identifying_element(|e| *e); None of the std collections `BTreeSet` and `HashSet` retain insertion order, `Vec` retains insertion order, however, it allows for duplicates. So if you want a collection of unique elements (Set-like) that does retain insertion order, `IdentifiedVec` suits your needs. Even better, the elements does not need to be to impl `Hash` nor ` Ord`. -# Features - -## Serde +## Flags -The `IdentifiedVecOf` type (which `Element` impl `Identifiable` trait) is `serde::Serializable` and `serde::Deserializable` as `Vec`. +This crate has the following Cargo features: -```toml -identified_vec = { version = "0.1.2", features = ["serde"] } -``` +- `serde`: Enables serde serialization support on `IdentifiedVecOf` type (which `Element` impl `Identifiable` trait). +- `id_prim`: Get impl of trait `Identifiable` for primitives: `i8`,.., `i128`, `u8`, ..., `u128` and `bool` (not so useful, allows for only two elements in `IdentifiedVecOf`, but who am I to discriminate.) ## Implementation Details -An identified vec consists of a `Vec` of `ID`s keeping insertion order and a `HashMap` of id-element pairs, for contsant time lookip of element given an ID. +An identified vec consists of a `Vec` of `ID`s keeping insertion order and a `HashMap` of id-element pairs, for constant time lookup of element given an ID. + +## License + +Licensed under MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) diff --git a/src/identifiable.rs b/src/identifiable_trait.rs similarity index 100% rename from src/identifiable.rs rename to src/identifiable_trait.rs diff --git a/src/lib.rs b/src/lib.rs index a82ddcd..95cfdd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,158 @@ -pub mod identifiable; -pub mod identified_vec; -pub mod identified_vec_of; +//! A collection of unique identifiable elements which retains **insertion** order, inspired by [Pointfree's Swift Identified Collections](https://github.com/pointfreeco/swift-identified-collections). +//! +//! Similar to the standard `Vec`, the `IdentifiedVec` maintain their elements in a particular user-specified order. However, unlike `Vec`, the `IdentifiedVec` introduce the ability to uniquely identify elements, using a hash table to ensure that no two elements have the same identity, and to efficiently look up elements corresponding to specific identifiers. +//! +//! `IdentifiedVec` is a useful alternative to `Vec` when you need to be able to efficiently access unique elements by a stable identifier. It is also a useful alternative to `BTreeSet`, where the `Ord` trait requirement may be too strict, an a useful alternative to `HashSet` where `Hash` trait requirement may be too strict. +//! +//! You can create an identified vec with any element type that implements the `Identifiable` trait. +//! +//! # Example +//! +//! ``` +//! extern crate identified_vec; +//! use identified_vec::{IdentifiedVec, IdentifiedVecOf, Identifiable}; +//! use std::cell::RefCell; +//! +//! #[derive(Eq, PartialEq, Clone, Debug)] +//! struct User { +//! id: &'static str, +//! name: RefCell<&'static str>, +//! } +//! +//! impl User { +//! fn new(id: &'static str, name: &'static str) -> Self { +//! Self { +//! id, +//! name: RefCell::new(name), +//! } +//! } +//! fn name(&self) -> &'static str { +//! *self.name.borrow() +//! } +//! } +//! +//! impl Identifiable for User { +//! type ID = &'static str; +//! fn id(&self) -> Self::ID { +//! self.id +//! } +//! } +//! +//! let mut users = IdentifiedVecOf::::from_iter([ +//! User::new("u_42", "Satoshi Nakamoto"), +//! User::new("u_1337", "Leia Skywalker"), +//! ]); +//! +//! assert_eq!( +//! users.get(&"u_42").map(|u| u.name()), +//! Some("Satoshi Nakamoto") +//! ); +//! +//! assert_eq!( +//! users.get_at_index(1).map(|u| u.name()), +//! Some("Leia Skywalker") +//! ); +//! +//! users.append(User::new("u_237", "Alan Turing")); +//! assert_eq!( +//! users.elements(), +//! [ +//! User::new("u_42", "Satoshi Nakamoto"), +//! User::new("u_1337", "Leia Skywalker"), +//! User::new("u_237", "Alan Turing"), +//! ] +//! .iter() +//! .collect::>() +//! ); +//! +//! // Element with same ID is not appended: +//! users.append(User::new("u_42", "Tom Mervolo Dolder")); +//! assert_eq!( +//! users.elements(), +//! [ +//! User::new("u_42", "Satoshi Nakamoto"), +//! User::new("u_1337", "Leia Skywalker"), +//! User::new("u_237", "Alan Turing"), +//! ] +//! .iter() +//! .collect::>() +//! ); +//! +//! // Element with same ID replaces existing if an `update_*` method is used: +//! // e.g. `update_or_insert`: +//! users.update_or_insert(User::new("u_42", "Tom Mervolo Dolder"), 0); +//! assert_eq!( +//! users.elements(), +//! [ +//! User::new("u_42", "Tom Mervolo Dolder"), +//! User::new("u_1337", "Leia Skywalker"), +//! User::new("u_237", "Alan Turing"), +//! ] +//! .iter() +//! .collect::>() +//! ); +//! +//! // or `update_or_append` +//! users.update_or_append(User::new("u_237", "Marie Curie")); +//! assert_eq!( +//! users.elements(), +//! [ +//! User::new("u_42", "Tom Mervolo Dolder"), +//! User::new("u_1337", "Leia Skywalker"), +//! User::new("u_237", "Marie Curie"), +//! ] +//! .iter() +//! .collect::>() +//! ); +//! +//! // or mutate with `get_mut(id)` +//! *users.get_mut(&"u_1337").unwrap().name.get_mut() = "Yoda"; +//! assert_eq!( +//! users.elements(), +//! [ +//! User::new("u_42", "Tom Mervolo Dolder"), +//! User::new("u_1337", "Yoda"), +//! User::new("u_237", "Marie Curie"), +//! ] +//! .iter() +//! .collect::>() +//! ); +//! ``` +//! +//! Or you can provide a closure that describes an element's identity: +//! +//! ``` +//! extern crate identified_vec; +//! use identified_vec::{IdentifiedVec, IdentifiedVecOf, Identifiable}; +//! let numbers = IdentifiedVec::::new_identifying_element(|e| *e); +//! ``` -#[cfg(feature = "serde")] -pub mod serde_error; +mod identifiable_trait; +mod primitives_identifiable; +mod serde_error; +mod vec; +mod vec_of; + +pub mod identified_vec { + //! A collection of unique identifiable elements which retains **insertion** order. + pub use crate::vec::*; +} + +pub mod identified_vec_of { + //! The `Identifiable` trait allows you to use the + //! `IdentifiedVecOf instead of the more verbose + //! `IdentifiedVec` but also allows you to + //! skip the `id_of_element: fn(&Element) -> ID` closure when + //! initializing a new identified vec. + pub use crate::identifiable_trait::*; + pub use crate::vec_of::*; + + #[cfg(feature = "id_prim")] + pub use crate::primitives_identifiable::*; + + #[cfg(feature = "serde")] + pub use crate::serde_error::*; +} + +pub use crate::identified_vec::*; +pub use crate::identified_vec_of::*; diff --git a/src/primitives_identifiable.rs b/src/primitives_identifiable.rs new file mode 100644 index 0000000..1081c11 --- /dev/null +++ b/src/primitives_identifiable.rs @@ -0,0 +1,26 @@ +#![cfg(feature = "id_prim")] + +use crate::identified_vec_of::Identifiable; + +macro_rules! impl_id { + ($primitive_type:ident) => { + impl Identifiable for $primitive_type { + type ID = $primitive_type; + fn id(&self) -> Self::ID { + *self + } + } + }; +} + +impl_id!(i8); +impl_id!(i16); +impl_id!(i32); +impl_id!(i64); +impl_id!(i128); +impl_id!(u8); +impl_id!(u16); +impl_id!(u32); +impl_id!(u64); +impl_id!(u128); +impl_id!(bool); diff --git a/src/identified_vec.rs b/src/vec.rs similarity index 63% rename from src/identified_vec.rs rename to src/vec.rs index 64c6117..5f27f04 100644 --- a/src/identified_vec.rs +++ b/src/vec.rs @@ -33,9 +33,7 @@ pub enum ConflictResolutionChoice { /// /// ``` /// extern crate identified_vec; -/// use identified_vec::identified_vec::IdentifiedVec; -/// use identified_vec::identifiable::Identifiable; -/// use identified_vec::identified_vec_of::IdentifiedVecOf; +/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; /// use std::cell::RefCell; /// /// #[derive(Eq, PartialEq, Clone, Debug)] @@ -146,9 +144,7 @@ pub enum ConflictResolutionChoice { /// /// ``` /// /// extern crate identified_vec; -/// use identified_vec::identified_vec::IdentifiedVec; -/// use identified_vec::identifiable::Identifiable; -/// use identified_vec::identified_vec_of::IdentifiedVecOf; +/// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; /// /// let numbers = IdentifiedVec::::new_identifying_element(|e| *e); /// ``` @@ -363,9 +359,7 @@ where /// /// ``` /// extern crate identified_vec; - /// use identified_vec::identified_vec::IdentifiedVec; - /// use identified_vec::identifiable::Identifiable; - /// use identified_vec::identified_vec_of::IdentifiedVecOf; + /// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; /// /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] /// struct User { @@ -449,6 +443,7 @@ where } } +/// An iterator over the items of an `IdentifiedVec`. pub struct IdentifiedVecIterator<'a, ID, Element> where ID: Eq + Hash + Clone + Debug, @@ -485,6 +480,8 @@ where } } } + +/// An owning iterator over the items of an `IdentifiedVec`. pub struct IdentifiedVecIntoIterator where ID: Eq + Hash + Clone + Debug, @@ -644,9 +641,7 @@ where /// /// ``` /// extern crate identified_vec; - /// use identified_vec::identified_vec::IdentifiedVec; - /// use identified_vec::identifiable::Identifiable; - /// use identified_vec::identified_vec_of::IdentifiedVecOf; + /// use identified_vec::{IdentifiedVec, Identifiable, IdentifiedVecOf}; /// /// #[derive(Eq, PartialEq, Clone, Debug, Hash)] /// struct User { @@ -879,558 +874,3 @@ where format!("order: {:?}\nelements: {:?}", self.order, self.elements) } } - -#[cfg(test)] -mod tests { - - use std::{cell::RefCell, collections::HashSet, fmt::Debug}; - - use anyerror::AnyError; - - use crate::{ - identifiable::Identifiable, - identified_vec::{ConflictResolutionChoice, IdentifiedVec}, - identified_vec_of::IdentifiedVecOf, - serde_error::IdentifiedVecOfSerdeFailure, - }; - - #[derive(Eq, PartialEq, Clone)] - pub struct User { - pub id: i32, - pub name: RefCell, - } - impl User { - fn new(id: i32, name: &str) -> Self { - if name.is_empty() { - panic!("name cannot be empty") - } - Self { - id, - name: RefCell::new(name.to_string()), - } - } - - pub fn blob() -> Self { - User::new(1, "Blob") - } - pub fn blob_jr() -> Self { - User::new(2, "Blob, Jr.") - } - pub fn blob_sr() -> Self { - User::new(3, "Blob, Sr.") - } - } - impl Debug for User { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("User") - .field("id", &self.id) - .field("name", &self.name.borrow()) - .finish() - } - } - impl Identifiable for User { - type ID = i32; - fn id(&self) -> Self::ID { - self.id - } - } - - impl Identifiable for i32 { - type ID = i32; - fn id(&self) -> Self::ID { - return self.clone(); - } - } - type SUT = IdentifiedVecOf; - type Users = IdentifiedVecOf; - - #[test] - fn new_is_empty() { - assert_eq!(SUT::new().len(), 0); - } - - #[test] - fn ids() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.ids(), &[1, 2, 3]) - } - - #[test] - fn debug_str() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert!(identified_vec - .debug_str() - .starts_with("order: [1, 2, 3]\nelements: {"),) - } - - #[test] - fn elements() { - let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; - let identified_vec = Users::from_iter(vec.clone()); - assert_eq!( - identified_vec.elements(), - vec![&User::blob(), &User::blob_jr(), &User::blob_sr()] - ); - } - - #[test] - fn into_iter() { - let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; - let identified_vec = Users::from_iter(vec.clone()); - for (idx, element) in identified_vec.into_iter().enumerate() { - assert_eq!(vec[idx], element) - } - } - - #[test] - fn iter() { - let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; - let identified_vec = Users::from_iter(vec.clone()); - for (idx, element) in identified_vec.iter().enumerate() { - assert_eq!(&vec[idx], element) - } - } - - #[test] - fn get() { - let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; - let mut identified_vec = Users::from_iter(vec.clone()); - assert_eq!(identified_vec.get(&1), Some(&User::blob())); - assert_eq!(identified_vec.get(&2), Some(&User::blob_jr())); - assert_eq!(identified_vec.get(&3), Some(&User::blob_sr())); - - // 1 - let mut id: &i32 = &1; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .push_str(", Esq."); - assert_eq!( - identified_vec.get(id), - Some(&User::new(id.clone(), "Blob, Esq.")) - ); - - // 2 - id = &2; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .drain(4..9); - assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); - - // 3 - id = &3; - identified_vec - .get_mut(id) - .unwrap() - .name - .borrow_mut() - .drain(4..9); - assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); - - identified_vec.remove_by_id(id); - assert_eq!(identified_vec.get(id), None); - identified_vec.append(User::new(4, "Blob, Sr.")); - assert_eq!( - identified_vec.elements(), - [ - User::new(1, "Blob, Esq."), - User::new(2, "Blob"), - User::new(4, "Blob, Sr."), - ] - .iter() - .collect::>() - ); - } - - #[test] - fn contains_element() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert!(identified_vec.contains(&2)) - } - - #[test] - fn remove_by_id_not_present() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - assert!(identified_vec.remove_by_id(&5).is_none()); - } - - #[test] - fn get_at_index() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.get_at_index(2), Some(&3)); - assert_eq!(identified_vec.get_at_index(999), None); - } - - #[test] - fn contains_id() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert!(identified_vec.contains_id(&1)); - assert_eq!(identified_vec.contains_id(&999), false); - } - - #[test] - fn index_id() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.index_of_id(&2), Some(1)); - } - - #[test] - fn remove_element() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.remove(&2), Some(2)); - assert_eq!(identified_vec.elements(), [&1, &3]); - } - - #[test] - fn remove_by_id() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.remove_by_id(&2), Some(2)); - assert_eq!(identified_vec.elements(), [&1, &3]); - } - - #[test] - fn constructor_id_uniquing_elements() { - #[derive(Eq, PartialEq, Clone, Hash, Debug)] - struct Model { - id: i32, - data: &'static str, - } - impl Model { - fn new(id: i32, data: &'static str) -> Self { - Self { id, data } - } - } - - let conservative = IdentifiedVec::::new_from_iter_uniquing_ids_with( - [ - Model::new(1, "A"), - Model::new(2, "B"), - Model::new(1, "AAAA"), - ], - |e| e.id, - |_| ConflictResolutionChoice::ChooseFirst, - ); - - assert_eq!( - conservative.elements(), - [&Model::new(1, "A"), &Model::new(2, "B")] - ); - - let progressive = IdentifiedVec::::new_from_iter_uniquing_ids_with( - [ - Model::new(1, "A"), - Model::new(2, "B"), - Model::new(1, "AAAA"), - ], - |e| e.id, - |_| ConflictResolutionChoice::ChooseLast, - ); - - assert_eq!( - progressive.elements(), - [&Model::new(1, "AAAA"), &Model::new(2, "B")] - ) - } - - #[test] - fn constructor_uniquing_elements() { - #[derive(Eq, PartialEq, Clone, Hash, Debug)] - struct Model { - id: i32, - data: &'static str, - } - impl Model { - fn new(id: i32, data: &'static str) -> Self { - Self { id, data } - } - } - impl Identifiable for Model { - type ID = i32; - - fn id(&self) -> Self::ID { - self.id - } - } - - let conservative = IdentifiedVecOf::::new_from_iter_uniquing_with( - [ - Model::new(1, "A"), - Model::new(2, "B"), - Model::new(1, "AAAA"), - ], - |_| ConflictResolutionChoice::ChooseFirst, - ); - - assert_eq!( - conservative.elements(), - [&Model::new(1, "A"), &Model::new(2, "B")] - ); - - let progressive = IdentifiedVecOf::::new_from_iter_uniquing_with( - [ - Model::new(1, "A"), - Model::new(2, "B"), - Model::new(1, "AAAA"), - ], - |_| ConflictResolutionChoice::ChooseLast, - ); - - assert_eq!( - progressive.elements(), - [&Model::new(1, "AAAA"), &Model::new(2, "B")] - ) - } - - #[test] - fn append() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - let (mut inserted, mut index) = identified_vec.append(4); - assert!(inserted); - assert_eq!(index, 3); - assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); - (inserted, index) = identified_vec.append(2); - assert_eq!(inserted, false); - assert_eq!(index, 1); - assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); - } - - #[test] - fn append_other() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - identified_vec.append_other([1, 4, 3, 5]); - assert_eq!(identified_vec.elements(), [&1, &2, &3, &4, &5]) - } - - #[test] - fn insert() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - let (mut inserted, mut index) = identified_vec.insert(0, 0); - assert!(inserted); - assert_eq!(index, 0); - assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); - (inserted, index) = identified_vec.insert(2, 0); - assert_eq!(inserted, false); - assert_eq!(index, 2); - assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); - } - - #[test] - fn update_at() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.update_at(2, 1), 2) - } - - #[test] - #[should_panic(expected = "Expected element at index {index}")] - fn update_at_expect_panic_unknown_index() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - identified_vec.update_at(0, 999); - } - - #[test] - #[should_panic(expected = "The replacement item must match the identity of the original")] - fn update_at_expect_panic_other_id() { - struct User { - id: &'static str, - name: &'static str, - } - impl Identifiable for User { - type ID = &'static str; - fn id(&self) -> Self::ID { - self.id - } - } - impl User { - fn new(id: &'static str, name: &'static str) -> Self { - Self { id, name } - } - } - let mut identified_vec = IdentifiedVecOf::::new(); - identified_vec.append(User::new("u_42", "Zelda")); - assert_eq!(identified_vec.get_at_index(0).unwrap().name, "Zelda"); - identified_vec.update_at(User::new("u_999999", "Zelda"), 0); - } - - #[test] - fn update_or_append() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(identified_vec.update_or_append(4), None); - assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); - assert_eq!(identified_vec.update_or_append(2), Some(2)); - } - - #[test] - fn update_or_insert() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - let (mut original_member, mut index) = identified_vec.update_or_insert(0, 0); - assert_eq!(original_member, None); - assert_eq!(index, 0); - assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); - (original_member, index) = identified_vec.update_or_insert(2, 0); - assert_eq!(original_member, Some(2)); - assert_eq!(index, 2); - assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]) - } - - #[test] - fn remove_at_offsets() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - identified_vec.remove_at_offsets([0, 2]); - assert_eq!(identified_vec.elements(), [&2]) - } - - #[test] - #[should_panic(expected = "Precondition failure, index out of bounds")] - fn remove_at_out_of_bounds() { - let mut identified_vec = SUT::from_iter([1, 2, 3]); - identified_vec.remove_at(999); - } - - #[test] - fn serde() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!( - serde_json::to_value(identified_vec.clone()) - .and_then(|j| serde_json::from_value::(j)) - .unwrap(), - identified_vec - ); - assert_eq!( - serde_json::from_str::("[1,2,3]").unwrap(), - identified_vec - ); - assert_eq!(serde_json::to_string(&identified_vec).unwrap(), "[1,2,3]"); - assert_eq!( - serde_json::from_str::("[1,1,1]").expect_err("should fail").to_string(), - "identified_vec::serde_error::IdentifiedVecOfSerdeFailure: Duplicate element at offset 1" - ); - - assert!(serde_json::from_str::("invalid").is_err(),); - } - - #[test] - fn serde_via_vec() { - let vec = vec![1, 2, 3]; - let json_from_vec = serde_json::to_value(vec).unwrap(); - let mut identified_vec = serde_json::from_value::(json_from_vec).unwrap(); - identified_vec.append(9); - let json_from_identified_vec = serde_json::to_value(identified_vec).unwrap(); - let vec_from_json = serde_json::from_value::>(json_from_identified_vec).unwrap(); - assert_eq!(vec_from_json, vec![1, 2, 3, 9]); - } - - #[test] - fn eq() { - #[derive(Eq, PartialEq, Clone, Hash, Debug)] - struct Foo { - id: &'static str, - value: String, - } - impl Foo { - fn with(id: &'static str, value: String) -> Self { - Self { id, value } - } - fn new() -> Self { - Self::with("id", "value".to_string()) - } - } - impl Identifiable for Foo { - type ID = &'static str; - - fn id(&self) -> Self::ID { - self.id - } - } - - // Create `IdentifiedVec` using all of the initializers - let mut vecs: Vec> = vec![ - IdentifiedVecOf::new(), - IdentifiedVecOf::new_identifying_element(|e| e.id()), - IdentifiedVecOf::new_from_iter_uniquing_with([], |_| { - ConflictResolutionChoice::ChooseLast - }), - IdentifiedVecOf::new_from_iter_uniquing_ids_with( - [], - |e| e.id(), - |_| ConflictResolutionChoice::ChooseLast, - ), - IdentifiedVecOf::new_from_iter_try_uniquing_ids_with( - [], - |e: &Foo| e.id(), - |_| Ok(ConflictResolutionChoice::ChooseLast), - ) - .unwrap(), - ]; - - assert_eq!( - IdentifiedVecOf::new_from_iter_try_uniquing_ids_with( - [Foo::new(), Foo::new()], - |e: &Foo| e.id(), - |_| Err(AnyError::new( - &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) - )), - ), - Err(AnyError::new( - &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) - )) - ); - - assert_eq!( - IdentifiedVecOf::new_from_iter_try_uniquing_with([Foo::new(), Foo::new()], |_| Err( - AnyError::new(&IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1)) - ),), - Err(AnyError::new( - &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) - )) - ); - - vecs.iter().for_each(|l| { - vecs.iter().for_each(|r| { - assert_eq!(l, r); - }) - }); - - // add an element to each identified_vec - vecs.iter_mut().for_each(|v| _ = v.append(Foo::new())); - - vecs.iter().for_each(|l| { - vecs.iter().for_each(|r| { - assert_eq!(l, r); - }) - }); - - // modify all arrays - vecs.iter_mut() - .enumerate() - .for_each(|(i, v)| _ = v.append(Foo::with("id2", format!("{i}")))); - - vecs.iter().enumerate().for_each(|l| { - vecs.iter().enumerate().for_each(|r| { - if l.0 != r.0 { - // println!("l='{}', r='{}'", l, r); - assert_ne!(l, r) - } - }) - }); - } - - #[test] - fn display() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!(format!("{}", identified_vec), "[1, 2, 3]"); - } - - #[test] - fn hash() { - let identified_vec = SUT::from_iter([1, 2, 3]); - assert_eq!( - HashSet::>::from_iter([identified_vec.clone()]), - HashSet::from_iter([identified_vec.clone(), identified_vec]) - ) - } -} diff --git a/src/identified_vec_of.rs b/src/vec_of.rs similarity index 98% rename from src/identified_vec_of.rs rename to src/vec_of.rs index a5be2e4..970b391 100644 --- a/src/identified_vec_of.rs +++ b/src/vec_of.rs @@ -1,14 +1,13 @@ -use crate::{ - identifiable::Identifiable, - identified_vec::{ConflictResolutionChoice, IdentifiedVec}, -}; - use anyerror::AnyError; use std::collections::HashMap; use std::fmt::Debug; #[cfg(feature = "serde")] use crate::serde_error::IdentifiedVecOfSerdeFailure; +use crate::{ + identified_vec_of::Identifiable, + vec::{ConflictResolutionChoice, IdentifiedVec}, +}; #[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..6cb5ee4 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,539 @@ +#![cfg(feature = "id_prim")] + +use std::{cell::RefCell, collections::HashSet, fmt::Debug, ops::Deref}; + +use anyerror::AnyError; +use identified_vec::{ + ConflictResolutionChoice, Identifiable, IdentifiedVec, IdentifiedVecOf, + IdentifiedVecOfSerdeFailure, +}; + +#[derive(Eq, PartialEq, Clone)] +pub struct User { + pub id: u16, + pub name: RefCell, +} + +impl User { + fn new(id: u16, name: &str) -> Self { + if name.is_empty() { + panic!("name cannot be empty") + } + Self { + id, + name: RefCell::new(name.to_string()), + } + } + + pub fn blob() -> Self { + User::new(1, "Blob") + } + pub fn blob_jr() -> Self { + User::new(2, "Blob, Jr.") + } + pub fn blob_sr() -> Self { + User::new(3, "Blob, Sr.") + } +} + +impl Debug for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("User") + .field("id", &self.id) + .field("name", &self.name.borrow()) + .finish() + } +} + +impl Identifiable for User { + type ID = u16; + fn id(&self) -> Self::ID { + self.id + } +} + +type SUT = IdentifiedVecOf; +type Users = IdentifiedVecOf; + +#[test] +fn new_is_empty() { + assert_eq!(SUT::new().len(), 0); +} + +#[test] +fn ids() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.ids(), &[1, 2, 3]) +} + +#[test] +fn debug_str() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert!(identified_vec + .debug_str() + .starts_with("order: [1, 2, 3]\nelements: {"),) +} + +#[test] +fn elements() { + let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; + let identified_vec = Users::from_iter(vec.clone()); + assert_eq!( + identified_vec.elements(), + vec![&User::blob(), &User::blob_jr(), &User::blob_sr()] + ); +} + +#[test] +fn into_iter() { + let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; + let identified_vec = Users::from_iter(vec.clone()); + for (idx, element) in identified_vec.into_iter().enumerate() { + assert_eq!(vec[idx], element) + } +} + +#[test] +fn iter() { + let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; + let identified_vec = Users::from_iter(vec.clone()); + for (idx, element) in identified_vec.iter().enumerate() { + assert_eq!(&vec[idx], element) + } +} + +#[test] +fn get() { + let vec = vec![User::blob(), User::blob_jr(), User::blob_sr()]; + let mut identified_vec = Users::from_iter(vec.clone()); + assert_eq!(identified_vec.get(&1), Some(&User::blob())); + assert_eq!(identified_vec.get(&2), Some(&User::blob_jr())); + assert_eq!(identified_vec.get(&3), Some(&User::blob_sr())); + + // 1 + let mut id: &u16 = &1; + identified_vec + .get_mut(id) + .unwrap() + .name + .borrow_mut() + .push_str(", Esq."); + assert_eq!( + identified_vec.get(id), + Some(&User::new(id.clone(), "Blob, Esq.")) + ); + + // 2 + id = &2; + identified_vec + .get_mut(id) + .unwrap() + .name + .borrow_mut() + .drain(4..9); + assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); + + // 3 + id = &3; + identified_vec + .get_mut(id) + .unwrap() + .name + .borrow_mut() + .drain(4..9); + assert_eq!(identified_vec.get(id), Some(&User::new(id.clone(), "Blob"))); + + identified_vec.remove_by_id(id); + assert_eq!(identified_vec.get(id), None); + identified_vec.append(User::new(4, "Blob, Sr.")); + assert_eq!( + identified_vec.elements(), + [ + User::new(1, "Blob, Esq."), + User::new(2, "Blob"), + User::new(4, "Blob, Sr."), + ] + .iter() + .collect::>() + ); +} + +#[test] +fn contains_element() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert!(identified_vec.contains(&2)) +} + +#[test] +fn remove_by_id_not_present() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + assert!(identified_vec.remove_by_id(&5).is_none()); +} + +#[test] +fn get_at_index() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.get_at_index(2), Some(&3)); + assert_eq!(identified_vec.get_at_index(999), None); +} + +#[test] +fn contains_id() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert!(identified_vec.contains_id(&1)); + assert_eq!(identified_vec.contains_id(&999), false); +} + +#[test] +fn index_id() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.index_of_id(&2), Some(1)); +} + +#[test] +fn remove_element() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.remove(&2), Some(2)); + assert_eq!(identified_vec.elements(), [&1, &3]); +} + +#[test] +fn remove_by_id() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.remove_by_id(&2), Some(2)); + assert_eq!(identified_vec.elements(), [&1, &3]); +} + +#[test] +fn constructor_id_uniquing_elements() { + #[derive(Eq, PartialEq, Clone, Hash, Debug)] + struct Model { + id: i32, + data: &'static str, + } + impl Model { + fn new(id: i32, data: &'static str) -> Self { + Self { id, data } + } + } + + let conservative = IdentifiedVec::::new_from_iter_uniquing_ids_with( + [ + Model::new(1, "A"), + Model::new(2, "B"), + Model::new(1, "AAAA"), + ], + |e| e.id, + |_| ConflictResolutionChoice::ChooseFirst, + ); + + assert_eq!( + conservative.elements(), + [&Model::new(1, "A"), &Model::new(2, "B")] + ); + + let progressive = IdentifiedVec::::new_from_iter_uniquing_ids_with( + [ + Model::new(1, "A"), + Model::new(2, "B"), + Model::new(1, "AAAA"), + ], + |e| e.id, + |_| ConflictResolutionChoice::ChooseLast, + ); + + assert_eq!( + progressive.elements(), + [&Model::new(1, "AAAA"), &Model::new(2, "B")] + ) +} + +#[test] +fn constructor_uniquing_elements() { + #[derive(Eq, PartialEq, Clone, Hash, Debug)] + struct Model { + id: i32, + data: &'static str, + } + impl Model { + fn new(id: i32, data: &'static str) -> Self { + Self { id, data } + } + } + impl Identifiable for Model { + type ID = i32; + + fn id(&self) -> Self::ID { + self.id + } + } + + let conservative = IdentifiedVecOf::::new_from_iter_uniquing_with( + [ + Model::new(1, "A"), + Model::new(2, "B"), + Model::new(1, "AAAA"), + ], + |_| ConflictResolutionChoice::ChooseFirst, + ); + + assert_eq!( + conservative.elements(), + [&Model::new(1, "A"), &Model::new(2, "B")] + ); + + let progressive = IdentifiedVecOf::::new_from_iter_uniquing_with( + [ + Model::new(1, "A"), + Model::new(2, "B"), + Model::new(1, "AAAA"), + ], + |_| ConflictResolutionChoice::ChooseLast, + ); + + assert_eq!( + progressive.elements(), + [&Model::new(1, "AAAA"), &Model::new(2, "B")] + ) +} + +#[test] +fn append() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + let (mut inserted, mut index) = identified_vec.append(4); + assert!(inserted); + assert_eq!(index, 3); + assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); + (inserted, index) = identified_vec.append(2); + assert_eq!(inserted, false); + assert_eq!(index, 1); + assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); +} + +#[test] +fn append_other() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + identified_vec.append_other([1, 4, 3, 5]); + assert_eq!(identified_vec.elements(), [&1, &2, &3, &4, &5]) +} + +#[test] +fn insert() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + let (mut inserted, mut index) = identified_vec.insert(0, 0); + assert!(inserted); + assert_eq!(index, 0); + assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); + (inserted, index) = identified_vec.insert(2, 0); + assert_eq!(inserted, false); + assert_eq!(index, 2); + assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); +} + +#[test] +fn update_at() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.update_at(2, 1), 2) +} + +#[test] +#[should_panic(expected = "Expected element at index {index}")] +fn update_at_expect_panic_unknown_index() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + identified_vec.update_at(0, 999); +} + +#[test] +#[should_panic(expected = "The replacement item must match the identity of the original")] +fn update_at_expect_panic_other_id() { + let mut identified_vec = IdentifiedVecOf::::new(); + identified_vec.append(User::new(32, "Zelda")); + assert_eq!( + identified_vec + .get_at_index(0) + .unwrap() + .name + .borrow() + .deref(), + "Zelda" + ); + identified_vec.update_at(User::new(999, "Zelda"), 0); +} + +#[test] +fn update_or_append() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(identified_vec.update_or_append(4), None); + assert_eq!(identified_vec.elements(), [&1, &2, &3, &4]); + assert_eq!(identified_vec.update_or_append(2), Some(2)); +} + +#[test] +fn update_or_insert() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + let (mut original_member, mut index) = identified_vec.update_or_insert(0, 0); + assert_eq!(original_member, None); + assert_eq!(index, 0); + assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]); + (original_member, index) = identified_vec.update_or_insert(2, 0); + assert_eq!(original_member, Some(2)); + assert_eq!(index, 2); + assert_eq!(identified_vec.elements(), [&0, &1, &2, &3]) +} + +#[test] +fn remove_at_offsets() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + identified_vec.remove_at_offsets([0, 2]); + assert_eq!(identified_vec.elements(), [&2]) +} + +#[test] +#[should_panic(expected = "Precondition failure, index out of bounds")] +fn remove_at_out_of_bounds() { + let mut identified_vec = SUT::from_iter([1, 2, 3]); + identified_vec.remove_at(999); +} + +#[test] +fn serde() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!( + serde_json::to_value(identified_vec.clone()) + .and_then(|j| serde_json::from_value::(j)) + .unwrap(), + identified_vec + ); + assert_eq!( + serde_json::from_str::("[1,2,3]").unwrap(), + identified_vec + ); + assert_eq!(serde_json::to_string(&identified_vec).unwrap(), "[1,2,3]"); + assert_eq!( + serde_json::from_str::("[1,1,1]") + .expect_err("should fail") + .to_string(), + "identified_vec::serde_error::IdentifiedVecOfSerdeFailure: Duplicate element at offset 1" + ); + + assert!(serde_json::from_str::("invalid").is_err(),); +} + +#[test] +fn serde_via_vec() { + let vec = vec![1, 2, 3]; + let json_from_vec = serde_json::to_value(vec).unwrap(); + let mut identified_vec = serde_json::from_value::(json_from_vec).unwrap(); + identified_vec.append(9); + let json_from_identified_vec = serde_json::to_value(identified_vec).unwrap(); + let vec_from_json = serde_json::from_value::>(json_from_identified_vec).unwrap(); + assert_eq!(vec_from_json, vec![1, 2, 3, 9]); +} + +#[test] +fn eq() { + #[derive(Eq, PartialEq, Clone, Hash, Debug)] + struct Foo { + id: &'static str, + value: String, + } + impl Foo { + fn with(id: &'static str, value: String) -> Self { + Self { id, value } + } + fn new() -> Self { + Self::with("id", "value".to_string()) + } + } + impl Identifiable for Foo { + type ID = &'static str; + + fn id(&self) -> Self::ID { + self.id + } + } + + // Create `IdentifiedVec` using all of the initializers + let mut vecs: Vec> = vec![ + IdentifiedVecOf::new(), + IdentifiedVecOf::new_identifying_element(|e| e.id()), + IdentifiedVecOf::new_from_iter_uniquing_with([], |_| ConflictResolutionChoice::ChooseLast), + IdentifiedVecOf::new_from_iter_uniquing_ids_with( + [], + |e| e.id(), + |_| ConflictResolutionChoice::ChooseLast, + ), + IdentifiedVecOf::new_from_iter_try_uniquing_ids_with( + [], + |e: &Foo| e.id(), + |_| Ok(ConflictResolutionChoice::ChooseLast), + ) + .unwrap(), + ]; + + assert_eq!( + IdentifiedVecOf::new_from_iter_try_uniquing_ids_with( + [Foo::new(), Foo::new()], + |e: &Foo| e.id(), + |_| Err(AnyError::new( + &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) + )), + ), + Err(AnyError::new( + &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) + )) + ); + + assert_eq!( + IdentifiedVecOf::new_from_iter_try_uniquing_with([Foo::new(), Foo::new()], |_| Err( + AnyError::new(&IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1)) + ),), + Err(AnyError::new( + &IdentifiedVecOfSerdeFailure::DuplicateElementsAtIndex(1) + )) + ); + + vecs.iter().for_each(|l| { + vecs.iter().for_each(|r| { + assert_eq!(l, r); + }) + }); + + // add an element to each identified_vec + vecs.iter_mut().for_each(|v| _ = v.append(Foo::new())); + + vecs.iter().for_each(|l| { + vecs.iter().for_each(|r| { + assert_eq!(l, r); + }) + }); + + // modify all arrays + vecs.iter_mut() + .enumerate() + .for_each(|(i, v)| _ = v.append(Foo::with("id2", format!("{i}")))); + + vecs.iter().enumerate().for_each(|l| { + vecs.iter().enumerate().for_each(|r| { + if l.0 != r.0 { + // println!("l='{}', r='{}'", l, r); + assert_ne!(l, r) + } + }) + }); +} + +#[test] +fn display() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!(format!("{}", identified_vec), "[1, 2, 3]"); +} + +#[test] +fn hash() { + let identified_vec = SUT::from_iter([1, 2, 3]); + assert_eq!( + HashSet::>::from_iter([identified_vec.clone()]), + HashSet::from_iter([identified_vec.clone(), identified_vec]) + ) +}