pub trait Concern: Send + Sync {}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ConcernRegistry {
concerns: Vec<String>,
}
impl ConcernRegistry {
#[must_use]
pub fn new() -> Self {
Self {
concerns: Vec::new(),
}
}
pub fn register(&mut self, name: impl Into<String>) {
let name = name.into();
if !self.includes(&name) {
self.concerns.push(name);
}
}
#[must_use]
pub fn includes(&self, name: &str) -> bool {
self.concerns.iter().any(|registered| registered == name)
}
#[must_use]
pub fn all(&self) -> &[String] {
&self.concerns
}
}
#[cfg(test)]
mod tests {
use super::{Concern, ConcernRegistry};
struct Auditable;
impl Concern for Auditable {}
#[test]
fn concern_registry_starts_empty() {
let registry = ConcernRegistry::new();
assert!(registry.all().is_empty());
assert!(!registry.includes("Auditable"));
}
#[test]
fn concern_registry_registers_names() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
assert!(registry.includes("Auditable"));
assert_eq!(registry.all(), &[String::from("Auditable")]);
}
#[test]
fn concern_registry_preserves_registration_order() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
registry.register("Trackable");
assert_eq!(
registry.all(),
&[String::from("Auditable"), String::from("Trackable")]
);
}
#[test]
fn concern_registry_ignores_duplicates() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
registry.register("Auditable");
assert_eq!(registry.all(), &[String::from("Auditable")]);
}
#[test]
fn concern_trait_can_be_implemented_by_marker_types() {
fn assert_concern<T: Concern>() {}
assert_concern::<Auditable>();
}
#[derive(Debug)]
struct Trackable;
impl Concern for Trackable {}
#[derive(Debug)]
struct Publishable;
impl Concern for Publishable {}
fn short_type_name<T>() -> String {
let full = std::any::type_name::<T>();
full.rsplit("::")
.next()
.map_or_else(|| full.to_string(), str::to_string)
}
#[test]
fn concern_registry_default_matches_new() {
assert_eq!(ConcernRegistry::default(), ConcernRegistry::new());
}
#[test]
fn concern_registry_clone_preserves_registered_names() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
registry.register("Trackable");
let clone = registry.clone();
assert_eq!(clone, registry);
assert_eq!(
clone.all(),
&[String::from("Auditable"), String::from("Trackable")]
);
}
#[test]
fn concern_registry_instances_are_independent() {
let mut first = ConcernRegistry::new();
let mut second = ConcernRegistry::new();
first.register("Auditable");
second.register("Trackable");
assert!(first.includes("Auditable"));
assert!(!first.includes("Trackable"));
assert!(second.includes("Trackable"));
assert!(!second.includes("Auditable"));
}
#[test]
fn concern_registry_registers_owned_strings() {
let mut registry = ConcernRegistry::new();
registry.register(String::from("Publishable"));
assert!(registry.includes("Publishable"));
assert_eq!(registry.all(), &[String::from("Publishable")]);
}
#[test]
fn concern_registry_includes_is_case_sensitive() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
assert!(registry.includes("Auditable"));
assert!(!registry.includes("auditable"));
}
#[test]
fn concern_registry_supports_multiple_marker_type_names() {
let mut registry = ConcernRegistry::new();
let names = [
short_type_name::<Auditable>(),
short_type_name::<Trackable>(),
short_type_name::<Publishable>(),
];
for name in &names {
registry.register(name.clone());
}
for name in &names {
assert!(registry.includes(name));
}
assert_eq!(registry.all(), &names);
}
#[test]
fn concern_registry_registers_empty_names_once() {
let mut registry = ConcernRegistry::new();
registry.register("");
registry.register(String::new());
assert!(registry.includes(""));
assert_eq!(registry.all(), &[String::new()]);
}
#[test]
fn concern_registry_preserves_first_occurrence_across_owned_and_borrowed_duplicates() {
let mut registry = ConcernRegistry::new();
let auditable = String::from("Auditable");
let trackable = String::from("Trackable");
registry.register(auditable.clone());
registry.register("Trackable");
registry.register(auditable);
registry.register(trackable);
assert_eq!(
registry.all(),
&[String::from("Auditable"), String::from("Trackable")]
);
}
#[test]
fn concern_registry_clone_remains_independent_after_additional_registration() {
let mut original = ConcernRegistry::new();
original.register("Auditable");
let mut clone = original.clone();
clone.register("Trackable");
assert_eq!(original.all(), &[String::from("Auditable")]);
assert_eq!(
clone.all(),
&[String::from("Auditable"), String::from("Trackable")]
);
}
#[test]
fn concern_registry_treats_empty_and_whitespace_names_as_distinct() {
let mut registry = ConcernRegistry::new();
registry.register("");
registry.register(" ");
assert!(registry.includes(""));
assert!(registry.includes(" "));
assert_eq!(registry.all(), &[String::new(), String::from(" ")]);
}
#[test]
fn concern_registry_preserves_first_occurrence_across_interleaved_duplicates() {
let mut registry = ConcernRegistry::new();
registry.register("Auditable");
registry.register("Trackable");
registry.register("Auditable");
registry.register("Publishable");
registry.register("Trackable");
assert_eq!(
registry.all(),
&[
String::from("Auditable"),
String::from("Trackable"),
String::from("Publishable"),
]
);
}
#[test]
fn multiple_marker_types_can_implement_concern_trait() {
fn assert_concern<T: Concern>() {}
assert_concern::<Auditable>();
assert_concern::<Trackable>();
assert_concern::<Publishable>();
}
}