I'm wondering what is the idiomatic way to create a specification pattern in Rust?
Let's say there is a WorkingDay
struct and two specifications should be created
IsActiveWorkingDaySpecification
IsInFutureWorkingDaySpecifictaion
My current approach looks like this
struct WorkingDay {
id: uuid::Uuid,
date: chrono::NaiveDate,
is_active: bool,
}
trait Specification<T> {
fn is_satisfied_by(&self, candidate: &T) -> bool;
}
struct IsActiveWorkingDaySpecification;
impl Specification<WorkingDay> for IsActiveWorkingDaySpecification {
fn is_satisfied_by(&self, candidate: &WorkingDay) -> bool {
candidate.is_active == true
}
}
struct IsInFutureWorkingDaySpecification;
impl Specification<WorkingDay> for IsInFutureWorkingDaySpecification {
fn is_satisfied_by(&self, candidate: &WorkingDay) -> bool {
chrono::Utc::now().date().naive_utc() < candidate.date
}
}
fn main() {
let working_day = WorkingDay {
id: uuid::Uuid::new_v4(),
date: chrono::NaiveDate::from_ymd(2077, 11, 24),
is_active: true,
};
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
let is_active = is_active_working_day_specification.is_satisfied_by(&working_day);
let is_in_future = is_future_working_day_specification.is_satisfied_by(&working_day);
println!("IsActive: {}", is_active);
println!("IsInFuture: {}", is_in_future);
}
The problem with this code is that the specifications cannot be composed. That is, if specification FutureActiveWorkingDaySpecification
needs to be created it forces manually compare results of existing specifications
// cut
fn main () {
// cut
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
let is_active = is_active_working_day_specification.is_satisfied_by(&working_day);
let is_in_future = is_future_working_day_specification.is_satisfied_by(&working_day);
let is_active_and_in_future = is_active && is_in_future; // AndSpecification
let is_active_or_in_future = is_active || is_in_future; // OrSpecification
// cut
}
I would like to achieve something like this, but don't know how
// cut
fn main () {
// cut
let is_active_working_day_specification = IsActiveWorkingDaySpecification {};
let is_future_working_day_specification = IsInFutureWorkingDaySpecification {};
// AndSpecification
let is_active_and_in_future = is_active_working_day_specification
.and(is_future_working_day_specification)
.is_satisfied_by(&working_day);
// OrSpecification
let is_active_or_in_future = is_active_working_day_specification
.or(is_future_working_day_specification)
.is_satisfied_by(&working_day);
// cut
}