11package com.itangcent.easyapi.rule
22
3+ import kotlinx.coroutines.flow.Flow
4+ import kotlinx.coroutines.flow.firstOrNull
5+ import kotlinx.coroutines.flow.toList
6+ import kotlinx.coroutines.flow.filter
7+ import kotlinx.coroutines.flow.mapNotNull
8+ import kotlinx.coroutines.flow.takeWhile
9+
310/* *
4- * Base interface for rule evaluation modes .
11+ * Defines how multiple rule values are aggregated into a single result .
512 *
6- * Determines how multiple rule values are aggregated when
7- * the same rule is defined in multiple configuration files.
13+ * @param T the type of values being aggregated
814 */
9- sealed interface RuleMode
15+ sealed interface RuleMode <T > {
16+ /* *
17+ * Aggregates multiple rule results into a single value.
18+ *
19+ * @param values The flow of results to aggregate
20+ * @return The aggregated result
21+ */
22+ suspend fun aggregate (values : Flow <RuleResult <T >>): T ?
23+ }
1024
1125/* *
1226 * Modes for rules that produce string values.
1327 *
1428 * Defines how multiple string values are combined.
1529 */
16- sealed class StringRuleMode : RuleMode {
17- /* *
18- * Aggregates multiple string values into a single result.
19- *
20- * @param values The list of values to aggregate
21- * @return The aggregated result
22- */
23- abstract fun aggregate (values : List <String ?>): String?
30+ sealed class StringRuleMode : RuleMode <String > {
31+ abstract override suspend fun aggregate (values : Flow <RuleResult <String >>): String?
2432
2533 /* *
2634 * Returns the first non-empty value.
2735 * Use for rules where only one value should be used.
2836 */
2937 data object SINGLE : StringRuleMode () {
30- override fun aggregate (values : List <String ?>): String? = values.firstOrNull { ! it.isNullOrEmpty() }
38+ override suspend fun aggregate (values : Flow <RuleResult <String >>): String? =
39+ values.mapNotNull { it.result }.firstOrNull { it.isNotEmpty() }
3140 }
3241
3342 /* *
3443 * Merges all non-empty values with newlines.
3544 * Use for rules where all values should be combined.
3645 */
3746 data object MERGE : StringRuleMode () {
38- override fun aggregate (values : List <String ?>): String? = values.filterNotNull().filter { it.isNotEmpty() }.joinToString(" \n " ).ifEmpty { null }
47+ override suspend fun aggregate (values : Flow <RuleResult <String >>): String? =
48+ values.mapNotNull { it.result }.filter { it.isNotEmpty() }.toList().joinToString(" \n " ).ifEmpty { null }
3949 }
4050
4151 /* *
4252 * Merges distinct non-empty values with newlines.
4353 * Use for rules where duplicate values should be removed.
4454 */
4555 data object MERGE_DISTINCT : StringRuleMode () {
46- override fun aggregate (values : List <String ?>): String? = values.filterNotNull().filter { it.isNotEmpty() }.distinct().joinToString(" \n " ).ifEmpty { null }
56+ override suspend fun aggregate (values : Flow <RuleResult <String >>): String? =
57+ values.mapNotNull { it.result }.filter { it.isNotEmpty() }.toList().distinct().joinToString(" \n " )
58+ .ifEmpty { null }
4759 }
4860}
4961
@@ -52,30 +64,25 @@ sealed class StringRuleMode : RuleMode {
5264 *
5365 * Defines how multiple boolean values are combined.
5466 */
55- sealed class BooleanRuleMode : RuleMode {
56- /* *
57- * Aggregates multiple boolean values into a single result.
58- *
59- * @param values The list of values to aggregate
60- * @return The aggregated result
61- */
62- abstract fun aggregate (values : List <Boolean ?>): Boolean
67+ sealed class BooleanRuleMode : RuleMode <Boolean > {
68+ abstract override suspend fun aggregate (values : Flow <RuleResult <Boolean >>): Boolean
6369
6470 /* *
6571 * Returns true if any value is true.
6672 * Use for rules like "ignore" or "required".
6773 */
6874 data object ANY : BooleanRuleMode () {
69- override fun aggregate (values : List <Boolean ?>): Boolean = values.any { it == true }
75+ override suspend fun aggregate (values : Flow <RuleResult <Boolean >>): Boolean =
76+ values.mapNotNull { it.result }.firstOrNull { it } != null
7077 }
7178
7279 /* *
7380 * Returns true only if all non-null values are true.
7481 * Use for rules that require all conditions to be met.
7582 */
7683 data object ALL : BooleanRuleMode () {
77- override fun aggregate (values : List < Boolean ? >): Boolean {
78- val nonNull = values.filterNotNull ()
84+ override suspend fun aggregate (values : Flow < RuleResult < Boolean > >): Boolean {
85+ val nonNull = values.mapNotNull { it.result }.toList ()
7986 return nonNull.isNotEmpty() && nonNull.all { it }
8087 }
8188 }
@@ -86,7 +93,10 @@ sealed class BooleanRuleMode : RuleMode {
8693 *
8794 * Event rules are executed for their side effects and don't produce values.
8895 */
89- sealed class EventRuleMode : RuleMode {
96+ sealed class EventRuleMode : RuleMode <Unit > {
97+
98+ abstract override suspend fun aggregate (values : Flow <RuleResult <Unit >>): Unit?
99+
90100 /* *
91101 * Whether to throw an exception when an event handler fails.
92102 */
@@ -96,13 +106,29 @@ sealed class EventRuleMode : RuleMode {
96106 * Ignores errors and continues execution.
97107 */
98108 data object IGNORE_ERROR : EventRuleMode () {
109+ override suspend fun aggregate (values : Flow <RuleResult <Unit >>): Unit? {
110+ values.collect {}
111+ return null
112+ }
113+
99114 override val throwOnError: Boolean = false
100115 }
101116
102117 /* *
103118 * Throws an exception on error.
104119 */
105120 data object THROW_IN_ERROR : EventRuleMode () {
121+ override suspend fun aggregate (values : Flow <RuleResult <Unit >>): Unit? {
122+ var error: Throwable ? = null
123+ values.collect { result ->
124+ if (result.error != null && error == null ) {
125+ error = result.error
126+ }
127+ }
128+ error?.let { throw it }
129+ return null
130+ }
131+
106132 override val throwOnError: Boolean = true
107133 }
108134}
@@ -112,12 +138,71 @@ sealed class EventRuleMode : RuleMode {
112138 *
113139 * Returns the first non-null value.
114140 */
115- data object IntRuleMode : RuleMode {
141+ data object IntRuleMode : RuleMode < Int > {
116142 /* *
117143 * Returns the first non-null integer value.
118144 *
119- * @param values The list of values to aggregate
145+ * @param values The flow of results to aggregate
120146 * @return The first non-null value, or null
121147 */
122- fun aggregate (values : List <Int ?>): Int? = values.firstOrNull { it != null }
148+ override suspend fun aggregate (values : Flow <RuleResult <Int >>): Int? =
149+ values.mapNotNull { it.result }.firstOrNull()
150+ }
151+
152+ /* *
153+ * Result of a rule evaluation.
154+ *
155+ * @param T the type of the result value
156+ */
157+ interface RuleResult <T > {
158+ /* *
159+ * The result value, or null if the rule didn't produce a value or failed.
160+ */
161+ val result: T ?
162+
163+ /* *
164+ * The error that occurred during evaluation, or null if successful.
165+ */
166+ val error: Throwable ?
167+
168+ companion object {
169+ /* *
170+ * Creates a successful result with the given value.
171+ */
172+ fun <T > success (result : T ? ): RuleResult <T > =
173+ if (result == null ) NULL () else Success (result)
174+
175+ /* *
176+ * Creates a failure result with the given error.
177+ */
178+ fun <T > failure (error : Throwable ): RuleResult <T > = Failure (error)
179+
180+ /* *
181+ * Creates a null result.
182+ */
183+ fun <T > NULL () = NullInstance as RuleResult <T >
184+ }
185+ }
186+
187+ /* *
188+ * A successful rule result.
189+ */
190+ data class Success <T >(override val result : T ) : RuleResult<T> {
191+ override val error: Throwable ?
192+ get() = null
193+ }
194+
195+ /* *
196+ * A failed rule result.
197+ */
198+ data class Failure <T >(override val error : Throwable ) : RuleResult<T> {
199+ override val result: T ? = null
200+ }
201+
202+ /* *
203+ * A null rule result.
204+ */
205+ object NullInstance : RuleResult<Any> {
206+ override val result = null
207+ override val error: Throwable ? = null
123208}
0 commit comments