1use std::collections::HashMap;
28use std::fmt::Debug;
29use std::marker::PhantomData;
30
31pub trait Specification<T>: Send + Sync {
42 type Error: Debug + Send;
44
45 fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error>;
47
48 fn and<S>(self, other: S) -> AndSpecification<Self, S, T>
50 where
51 Self: Sized,
52 S: Specification<T>,
53 {
54 AndSpecification::new(self, other)
55 }
56
57 fn or<S>(self, other: S) -> OrSpecification<Self, S, T>
59 where
60 Self: Sized,
61 S: Specification<T>,
62 {
63 OrSpecification::new(self, other)
64 }
65
66 fn not(self) -> NotSpecification<Self, T>
68 where
69 Self: Sized,
70 {
71 NotSpecification::new(self)
72 }
73}
74
75#[derive(Debug, Clone)]
81pub struct AndSpecification<L, R, T> {
82 left: L,
83 right: R,
84 _marker: PhantomData<T>,
85}
86
87impl<L, R, T> AndSpecification<L, R, T> {
88 pub fn new(left: L, right: R) -> Self {
89 Self {
90 left,
91 right,
92 _marker: PhantomData,
93 }
94 }
95}
96
97impl<L, R, T> Specification<T> for AndSpecification<L, R, T>
98where
99 L: Specification<T>,
100 R: Specification<T>,
101 T: Send + Sync,
102{
103 type Error = String;
104
105 fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
106 let left_result = self
107 .left
108 .is_satisfied_by(candidate)
109 .map_err(|e| format!("Left specification failed: {:?}", e))?;
110
111 if !left_result {
112 return Ok(false);
113 }
114
115 self.right
116 .is_satisfied_by(candidate)
117 .map_err(|e| format!("Right specification failed: {:?}", e))
118 }
119}
120
121#[derive(Debug, Clone)]
123pub struct OrSpecification<L, R, T> {
124 left: L,
125 right: R,
126 _marker: PhantomData<T>,
127}
128
129impl<L, R, T> OrSpecification<L, R, T> {
130 pub fn new(left: L, right: R) -> Self {
131 Self {
132 left,
133 right,
134 _marker: PhantomData,
135 }
136 }
137}
138
139impl<L, R, T> Specification<T> for OrSpecification<L, R, T>
140where
141 L: Specification<T>,
142 R: Specification<T>,
143 T: Send + Sync,
144{
145 type Error = String;
146
147 fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
148 let left_result = self
149 .left
150 .is_satisfied_by(candidate)
151 .map_err(|e| format!("Left specification failed: {:?}", e))?;
152
153 if left_result {
154 return Ok(true);
155 }
156
157 self.right
158 .is_satisfied_by(candidate)
159 .map_err(|e| format!("Right specification failed: {:?}", e))
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct NotSpecification<S, T> {
166 spec: S,
167 _marker: PhantomData<T>,
168}
169
170impl<S, T> NotSpecification<S, T> {
171 pub fn new(spec: S) -> Self {
172 Self {
173 spec,
174 _marker: PhantomData,
175 }
176 }
177}
178
179impl<S, T> Specification<T> for NotSpecification<S, T>
180where
181 S: Specification<T>,
182 T: Send + Sync,
183{
184 type Error = String;
185
186 fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
187 let result = self
188 .spec
189 .is_satisfied_by(candidate)
190 .map_err(|e| format!("Inner specification failed: {:?}", e))?;
191 Ok(!result)
192 }
193}
194
195#[derive(Debug, Clone)]
201pub struct SpecificationResult {
202 pub satisfied: bool,
204 pub specification_name: String,
206 pub message: String,
208 pub details: HashMap<String, String>,
210}
211
212impl SpecificationResult {
213 pub fn satisfied(name: impl Into<String>, message: impl Into<String>) -> Self {
215 Self {
216 satisfied: true,
217 specification_name: name.into(),
218 message: message.into(),
219 details: HashMap::new(),
220 }
221 }
222
223 pub fn unsatisfied(name: impl Into<String>, message: impl Into<String>) -> Self {
225 Self {
226 satisfied: false,
227 specification_name: name.into(),
228 message: message.into(),
229 details: HashMap::new(),
230 }
231 }
232
233 pub fn with_detail(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
235 self.details.insert(key.into(), value.into());
236 self
237 }
238}
239
240pub struct SpecificationEvaluator<T> {
246 _marker: PhantomData<T>,
247}
248
249impl<T: Send + Sync> SpecificationEvaluator<T> {
250 pub fn evaluate_all<S>(
252 specifications: &[&S],
253 candidate: &T,
254 ) -> Vec<Result<bool, String>>
255 where
256 S: Specification<T, Error = String> + ?Sized,
257 {
258 specifications
259 .iter()
260 .map(|spec| spec.is_satisfied_by(candidate))
261 .collect()
262 }
263
264 pub fn all_satisfied<S>(specifications: &[&S], candidate: &T) -> Result<bool, String>
266 where
267 S: Specification<T, Error = String> + ?Sized,
268 {
269 for spec in specifications {
270 if !spec.is_satisfied_by(candidate)? {
271 return Ok(false);
272 }
273 }
274 Ok(true)
275 }
276
277 pub fn any_satisfied<S>(specifications: &[&S], candidate: &T) -> Result<bool, String>
279 where
280 S: Specification<T, Error = String> + ?Sized,
281 {
282 for spec in specifications {
283 if spec.is_satisfied_by(candidate)? {
284 return Ok(true);
285 }
286 }
287 Ok(false)
288 }
289}
290
291#[derive(Debug, Clone, Default)]
297pub struct AlwaysTrue<T>(PhantomData<T>);
298
299impl<T> AlwaysTrue<T> {
300 pub fn new() -> Self {
301 Self(PhantomData)
302 }
303}
304
305impl<T: Send + Sync> Specification<T> for AlwaysTrue<T> {
306 type Error = std::convert::Infallible;
307
308 fn is_satisfied_by(&self, _candidate: &T) -> Result<bool, Self::Error> {
309 Ok(true)
310 }
311}
312
313#[derive(Debug, Clone, Default)]
315pub struct AlwaysFalse<T>(PhantomData<T>);
316
317impl<T> AlwaysFalse<T> {
318 pub fn new() -> Self {
319 Self(PhantomData)
320 }
321}
322
323impl<T: Send + Sync> Specification<T> for AlwaysFalse<T> {
324 type Error = std::convert::Infallible;
325
326 fn is_satisfied_by(&self, _candidate: &T) -> Result<bool, Self::Error> {
327 Ok(false)
328 }
329}
330
331pub struct PredicateSpecification<T, F>
333where
334 F: Fn(&T) -> bool + Send + Sync,
335{
336 predicate: F,
337 _marker: PhantomData<T>,
338}
339
340impl<T, F> PredicateSpecification<T, F>
341where
342 F: Fn(&T) -> bool + Send + Sync,
343{
344 pub fn new(predicate: F) -> Self {
345 Self {
346 predicate,
347 _marker: PhantomData,
348 }
349 }
350}
351
352impl<T, F> Specification<T> for PredicateSpecification<T, F>
353where
354 T: Send + Sync,
355 F: Fn(&T) -> bool + Send + Sync,
356{
357 type Error = std::convert::Infallible;
358
359 fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
360 Ok((self.predicate)(candidate))
361 }
362}
363
364pub fn predicate<T, F>(f: F) -> PredicateSpecification<T, F>
366where
367 F: Fn(&T) -> bool + Send + Sync,
368{
369 PredicateSpecification::new(f)
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[derive(Debug)]
377 struct TestEntity {
378 value: i32,
379 active: bool,
380 }
381
382 struct PositiveValue;
383 impl Specification<TestEntity> for PositiveValue {
384 type Error = String;
385 fn is_satisfied_by(&self, e: &TestEntity) -> Result<bool, Self::Error> {
386 Ok(e.value > 0)
387 }
388 }
389
390 struct IsActive;
391 impl Specification<TestEntity> for IsActive {
392 type Error = String;
393 fn is_satisfied_by(&self, e: &TestEntity) -> Result<bool, Self::Error> {
394 Ok(e.active)
395 }
396 }
397
398 #[test]
399 fn test_and_specification() {
400 let spec = PositiveValue.and(IsActive);
401
402 let active_positive = TestEntity { value: 10, active: true };
403 assert!(spec.is_satisfied_by(&active_positive).unwrap());
404
405 let inactive_positive = TestEntity { value: 10, active: false };
406 assert!(!spec.is_satisfied_by(&inactive_positive).unwrap());
407
408 let active_negative = TestEntity { value: -5, active: true };
409 assert!(!spec.is_satisfied_by(&active_negative).unwrap());
410 }
411
412 #[test]
413 fn test_or_specification() {
414 let spec = PositiveValue.or(IsActive);
415
416 let inactive_positive = TestEntity { value: 10, active: false };
417 assert!(spec.is_satisfied_by(&inactive_positive).unwrap());
418
419 let active_negative = TestEntity { value: -5, active: true };
420 assert!(spec.is_satisfied_by(&active_negative).unwrap());
421
422 let inactive_negative = TestEntity { value: -5, active: false };
423 assert!(!spec.is_satisfied_by(&inactive_negative).unwrap());
424 }
425
426 #[test]
427 fn test_not_specification() {
428 let spec = PositiveValue.not();
429
430 let positive = TestEntity { value: 10, active: false };
431 assert!(!spec.is_satisfied_by(&positive).unwrap());
432
433 let negative = TestEntity { value: -5, active: false };
434 assert!(spec.is_satisfied_by(&negative).unwrap());
435 }
436
437 #[test]
438 fn test_predicate_specification() {
439 let spec = predicate(|e: &TestEntity| e.value > 5);
440
441 let high = TestEntity { value: 10, active: false };
442 assert!(spec.is_satisfied_by(&high).unwrap());
443
444 let low = TestEntity { value: 3, active: false };
445 assert!(!spec.is_satisfied_by(&low).unwrap());
446 }
447
448 #[test]
449 fn test_complex_composition() {
450 let spec = PositiveValue
452 .and(IsActive)
453 .or(predicate(|e: &TestEntity| e.value > 100));
454
455 let active_positive = TestEntity { value: 10, active: true };
456 assert!(spec.is_satisfied_by(&active_positive).unwrap());
457
458 let inactive_very_high = TestEntity { value: 200, active: false };
459 assert!(spec.is_satisfied_by(&inactive_very_high).unwrap());
460
461 let inactive_low = TestEntity { value: 5, active: false };
462 assert!(!spec.is_satisfied_by(&inactive_low).unwrap());
463 }
464}