Skip to main content

ddd_toolkit_core/
specification.rs

1/// **English:** Fallible predicate over a borrowed candidate, with consuming boolean combinators.
2/// `T` may be unsized, but specification values must be sized. Combinations evaluate left to right,
3/// short-circuit on a decisive boolean, and propagate the first error without evaluating later rules.
4/// Implementations may use interior state; purity and thread safety are not required.
5///
6/// **日本語:** 借用した候補に対する失敗し得る述語と、自身を消費する論理結合操作です。
7/// `T` はサイズ不定でもよいですが、仕様の値自体はサイズ確定が必要です。結合は左から評価し、
8/// 真偽が確定すると短絡し、エラーでは後続の規則を評価せず最初のエラーを伝播します。
9/// 実装は内部状態を利用でき、純粋性やスレッド安全性は要求しません。
10///
11/// **English:** Combine rules over an unsized string candidate and invert the result.
12///
13/// **日本語:** サイズ不定の文字列候補に対する規則を結合し、結果を反転します。
14///
15/// ```rust
16/// use ddd_toolkit_core::Specification;
17///
18/// struct NonEmpty;
19/// impl Specification<str> for NonEmpty {
20///     type Error = std::convert::Infallible;
21///     fn is_satisfied_by(&self, candidate: &str) -> Result<bool, Self::Error> {
22///         Ok(!candidate.is_empty())
23///     }
24/// }
25/// let rule = NonEmpty.and(NonEmpty).not();
26/// assert_eq!(rule.is_satisfied_by("order"), Ok(false));
27/// assert_eq!(rule.is_satisfied_by(""), Ok(true));
28/// ```
29pub trait Specification<T: ?Sized>: Sized {
30    /// **English:** Evaluation failure, distinct from a successfully evaluated `false` result.
31    ///
32    /// **日本語:** 正常に評価された `false` とは区別される、評価の失敗です。
33    type Error;
34
35    /// **English:** Returns whether `candidate` satisfies the rule or why evaluation failed.
36    /// The candidate remains borrowed; side effects and panic conditions depend on the rule.
37    ///
38    /// **日本語:** `candidate` が規則を満たすか、評価が失敗した理由を返します。
39    /// 候補は借用のままで、副作用とパニックの条件は規則に依存します。
40    fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error>;
41
42    /// **English:** Consumes both rules to form [`And`], evaluating `other` only if this rule returns `Ok(true)`.
43    /// Both must share an error type. Construction evaluates neither rule.
44    ///
45    /// **日本語:** 両規則を消費して [`And`] を作り、この規則が `Ok(true)` の場合のみ `other` を評価します。
46    /// エラー型は共通でなければなりません。構築時にはどちらも評価しません。
47    fn and<S>(self, other: S) -> And<Self, S>
48    where
49        S: Specification<T, Error = Self::Error>,
50    {
51        And {
52            left: self,
53            right: other,
54        }
55    }
56
57    /// **English:** Consumes both rules to form [`Or`], evaluating `other` only if this rule returns `Ok(false)`.
58    /// An error from this rule is returned without consulting `other`.
59    ///
60    /// **日本語:** 両規則を消費して [`Or`] を作り、この規則が `Ok(false)` の場合のみ `other` を評価します。
61    /// この規則のエラーは `other` を評価せずに返します。
62    fn or<S>(self, other: S) -> Or<Self, S>
63    where
64        S: Specification<T, Error = Self::Error>,
65    {
66        Or {
67            left: self,
68            right: other,
69        }
70    }
71
72    /// **English:** Consumes this rule to form [`Not`], which flips successful booleans and preserves errors.
73    ///
74    /// **日本語:** この規則を消費して [`Not`] を作り、成功時の真偽を反転し、エラーは保持します。
75    fn not(self) -> Not<Self> {
76        Not { inner: self }
77    }
78}
79
80/// **English:** Conjunction that evaluates the right rule only after the left returns `Ok(true)`.
81/// A left `Ok(false)` or error is returned immediately; a right result is forwarded unchanged.
82///
83/// **日本語:** 左が `Ok(true)` を返した場合のみ右を評価する論理積です。
84/// 左の `Ok(false)` やエラーは直ちに返し、右を評価した場合はその結果を変更せず返します。
85pub struct And<L, R> {
86    left: L,
87    right: R,
88}
89
90impl<L, R> And<L, R> {
91    /// **English:** Stores `left` and `right` without evaluation; evaluation requires compatible specification impls.
92    ///
93    /// **日本語:** 評価せずに `left` と `right` を保持します。評価には互換な仕様の実装が必要です。
94    pub fn new(left: L, right: R) -> Self {
95        Self { left, right }
96    }
97}
98
99impl<T: ?Sized, L, R> Specification<T> for And<L, R>
100where
101    L: Specification<T>,
102    R: Specification<T, Error = L::Error>,
103{
104    type Error = L::Error;
105
106    fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
107        if !self.left.is_satisfied_by(candidate)? {
108            return Ok(false);
109        }
110        self.right.is_satisfied_by(candidate)
111    }
112}
113
114/// **English:** Disjunction that evaluates the right rule only after the left returns `Ok(false)`.
115/// A left `Ok(true)` or error short-circuits; errors are not treated as a negative match.
116///
117/// **日本語:** 左が `Ok(false)` を返した場合のみ右を評価する論理和です。
118/// 左の `Ok(true)` やエラーで短絡し、エラーを不一致として扱いません。
119pub struct Or<L, R> {
120    left: L,
121    right: R,
122}
123
124impl<L, R> Or<L, R> {
125    /// **English:** Owns the two operands in left-to-right evaluation order, without calling either rule.
126    ///
127    /// **日本語:** どちらも呼び出さず、左から右の評価順に二つの被演算子を所有します。
128    pub fn new(left: L, right: R) -> Self {
129        Self { left, right }
130    }
131}
132
133impl<T: ?Sized, L, R> Specification<T> for Or<L, R>
134where
135    L: Specification<T>,
136    R: Specification<T, Error = L::Error>,
137{
138    type Error = L::Error;
139
140    fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
141        if self.left.is_satisfied_by(candidate)? {
142            return Ok(true);
143        }
144        self.right.is_satisfied_by(candidate)
145    }
146}
147
148/// **English:** Negation that evaluates its inner rule once, flips `Ok(bool)`, and propagates `Err` unchanged.
149///
150/// **日本語:** 内側の規則を一度評価し、`Ok(bool)` を反転し、`Err` は変更せず伝播する否定です。
151pub struct Not<S> {
152    inner: S,
153}
154
155impl<S> Not<S> {
156    /// **English:** Wraps `inner` by value without evaluating or validating it.
157    ///
158    /// **日本語:** 評価や検証をせず、`inner` を値として包みます。
159    pub fn new(inner: S) -> Self {
160        Self { inner }
161    }
162}
163
164impl<T: ?Sized, S> Specification<T> for Not<S>
165where
166    S: Specification<T>,
167{
168    type Error = S::Error;
169
170    fn is_satisfied_by(&self, candidate: &T) -> Result<bool, Self::Error> {
171        self.inner.is_satisfied_by(candidate).map(|value| !value)
172    }
173}