comparable 0.5.4

A library for comparing data structures in Rust, oriented toward testing
Documentation
use std::collections::{BTreeSet, HashSet};
use std::fmt::Debug;
// use serde;

use crate::types::{Changed, Comparable};

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Debug)]
pub enum VecChange<Desc, Change> {
	Added(usize, Desc),
	Changed(usize, Change),
	Removed(usize, Desc),
}

impl<Value: PartialEq + Comparable> Comparable for Vec<Value> {
	type Desc = Vec<Value::Desc>;

	fn describe(&self) -> Self::Desc {
		self.iter().map(|x| x.describe()).collect()
	}

	type Change = Vec<VecChange<Value::Desc, Value::Change>>;

	fn comparison(&self, other: &Self) -> Changed<Self::Change> {
		let mut changes = Vec::new();
		let other_len = other.len();
		for i in 0..self.len() {
			if i >= other_len {
				changes.push(VecChange::Removed(i, self[i].describe()));
			} else if let Changed::Changed(change) = self[i].comparison(&other[i]) {
				changes.push(VecChange::Changed(i, change));
			}
		}
		if other.len() > self.len() {
			#[allow(clippy::needless_range_loop)]
			for i in self.len()..other.len() {
				changes.push(VecChange::Added(i, other[i].describe()));
			}
		}
		if changes.is_empty() {
			Changed::Unchanged
		} else {
			Changed::Changed(changes)
		}
	}
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Debug)]
pub enum SetChange<Desc> {
	Added(Desc),
	Removed(Desc),
}

impl<Value: Ord + Comparable> Comparable for BTreeSet<Value> {
	type Desc = Vec<Value::Desc>;

	fn describe(&self) -> Self::Desc {
		self.iter().map(|v| v.describe()).collect()
	}

	type Change = Vec<SetChange<Value::Desc>>;

	fn comparison(&self, other: &Self) -> Changed<Self::Change> {
		let mut changes = Vec::new();
		changes.append(
			&mut other
				.iter()
				.flat_map(|v| {
					if self.contains(v) {
						Changed::Unchanged
					} else {
						Changed::Changed(SetChange::Added(v.describe()))
					}
				})
				.collect(),
		);
		changes.append(
			&mut self
				.iter()
				.flat_map(|v| {
					if !other.contains(v) {
						Changed::Changed(SetChange::Removed(v.describe()))
					} else {
						Changed::Unchanged
					}
				})
				.collect(),
		);
		if changes.is_empty() {
			Changed::Unchanged
		} else {
			Changed::Changed(changes)
		}
	}
}

impl<Value: std::hash::Hash + Ord + Comparable> Comparable for HashSet<Value> {
	type Desc = Vec<Value::Desc>;

	fn describe(&self) -> Self::Desc {
		self.iter().map(|v| v.describe()).collect()
	}

	type Change = Vec<SetChange<Value::Desc>>;

	fn comparison(&self, other: &Self) -> Changed<Self::Change> {
		let mut changes = Vec::new();
		let mut others = other.iter().collect::<Vec<&Value>>();
		others.sort();
		changes.append(
			&mut others
				.iter()
				.flat_map(|v| {
					if self.contains(v) {
						Changed::Unchanged
					} else {
						Changed::Changed(SetChange::Added(v.describe()))
					}
				})
				.collect(),
		);
		let mut selfs = self.iter().collect::<Vec<&Value>>();
		selfs.sort();
		changes.append(
			&mut selfs
				.iter()
				.flat_map(|v| {
					if !other.contains(v) {
						Changed::Changed(SetChange::Removed(v.describe()))
					} else {
						Changed::Unchanged
					}
				})
				.collect(),
		);
		if changes.is_empty() {
			Changed::Unchanged
		} else {
			Changed::Changed(changes)
		}
	}
}