@@ -10,20 +10,35 @@ import dev.icerock.moko.resources.desc.desc
10
10
import kotlin.native.concurrent.ThreadLocal
11
11
import kotlin.reflect.KClass
12
12
13
- internal typealias ThrowableMapper = (Throwable ) -> Any
14
-
15
13
@Suppress(" TooManyFunctions" )
16
14
@ThreadLocal
17
15
object ExceptionMappersStorage {
18
16
19
- private val fallbackValuesMap: MutableMap <KClass <out Any >, Any > = mutableMapOf (
20
- StringDesc ::class to MR .strings.moko_errors_unknownError.desc()
17
+ private val containers: MutableMap <KClass <* >, MappersContainer <* >> = mutableMapOf (
18
+ StringDesc ::class to MappersContainer <StringDesc >(
19
+ mappers = emptyList(),
20
+ fallback = { MR .strings.moko_errors_unknownError.desc() }
21
+ )
21
22
)
23
+ private val notifiers: MutableList < (Throwable , KClass <* >, Any ) -> Unit > = mutableListOf ()
24
+
25
+ private fun <T : Any > getOrCreateContainer (resultClass : KClass <T >): MappersContainer <T > {
26
+ val existContainer: MappersContainer <* >? = containers[resultClass]
27
+ if (existContainer != null ) return existContainer as MappersContainer <T >
22
28
23
- private val mappersMap: MutableMap <KClass <out Any >, MutableMap <KClass <out Throwable >, ThrowableMapper >> =
24
- mutableMapOf ()
25
- private val conditionMappers: MutableMap <KClass <out Any >, MutableList <ConditionPair >> =
26
- mutableMapOf ()
29
+ return MappersContainer <T >(
30
+ mappers = emptyList(),
31
+ fallback = { throw FallbackValueNotFoundException (resultClass) }
32
+ ).also { containers[resultClass] = it }
33
+ }
34
+
35
+ private fun <T : Any > updateContainer (
36
+ resultClass : KClass <T >,
37
+ block : (MappersContainer <T >) -> MappersContainer <T >
38
+ ) {
39
+ val container: MappersContainer <T > = getOrCreateContainer(resultClass)
40
+ containers[resultClass] = block(container)
41
+ }
27
42
28
43
/* *
29
44
* Register simple mapper (E) -> T.
@@ -33,11 +48,16 @@ object ExceptionMappersStorage {
33
48
exceptionClass : KClass <E >,
34
49
mapper : (E ) -> T
35
50
): ExceptionMappersStorage {
36
- if (! mappersMap.containsKey(resultClass)) {
37
- mappersMap[resultClass] = mutableMapOf ()
51
+ updateContainer(
52
+ resultClass
53
+ ) { container ->
54
+ container.copy(
55
+ mappers = container.mappers + ThrowableMapperItem (
56
+ mapper = { mapper(it as E ) },
57
+ isApplied = { it::class == exceptionClass }
58
+ )
59
+ )
38
60
}
39
- @Suppress(" UNCHECKED_CAST" )
40
- mappersMap[resultClass]?.put(exceptionClass, mapper as ThrowableMapper )
41
61
return this
42
62
}
43
63
@@ -46,12 +66,19 @@ object ExceptionMappersStorage {
46
66
*/
47
67
fun <T : Any > register (
48
68
resultClass : KClass <T >,
49
- conditionPair : ConditionPair
69
+ isApplied : (Throwable ) -> Boolean ,
70
+ mapper : (Throwable ) -> T
50
71
): ExceptionMappersStorage {
51
- if (! conditionMappers.containsKey(resultClass)) {
52
- conditionMappers[resultClass] = mutableListOf ()
72
+ updateContainer(
73
+ resultClass
74
+ ) { container ->
75
+ container.copy(
76
+ mappers = container.mappers + ThrowableMapperItem (
77
+ mapper = mapper,
78
+ isApplied = isApplied
79
+ )
80
+ )
53
81
}
54
- conditionMappers[resultClass]?.add(conditionPair)
55
82
return this
56
83
}
57
84
@@ -76,10 +103,8 @@ object ExceptionMappersStorage {
76
103
noinline mapper : (Throwable ) -> T
77
104
): ExceptionMappersStorage = register(
78
105
resultClass = T ::class ,
79
- conditionPair = ConditionPair (
80
- condition,
81
- mapper as ThrowableMapper
82
- )
106
+ isApplied = condition,
107
+ mapper = mapper
83
108
)
84
109
85
110
/* *
@@ -91,19 +116,27 @@ object ExceptionMappersStorage {
91
116
*/
92
117
fun <E : Throwable , T : Any > find (
93
118
resultClass : KClass <T >,
94
- throwable : E ,
95
- exceptionClass : KClass <out E >
119
+ throwable : E
96
120
): ((E ) -> T )? {
97
- @Suppress(" UNCHECKED_CAST" )
98
- val mapper = conditionMappers[resultClass]
99
- ?.find { it.condition(throwable) }
100
- ?.mapper as ? ((E ) -> T )
101
- ? : mappersMap[resultClass]?.get(exceptionClass) as ? ((E ) -> T )
121
+ val container: MappersContainer <T >? = containers[resultClass] as MappersContainer <T >?
102
122
103
- return if (mapper == null && throwable !is Exception ) {
123
+ if (container == null && throwable !is Exception ) {
104
124
throw throwable
105
- } else {
106
- mapper
125
+ } else if (container == null ) {
126
+ return null
127
+ }
128
+
129
+ val mapper: (Throwable ) -> T = container.mappers
130
+ .firstOrNull { it.isApplied(throwable) }
131
+ ?.mapper
132
+ ? : container.fallback
133
+
134
+ return { exception ->
135
+ val result: T = mapper(exception)
136
+ notifiers.forEach { notifier ->
137
+ notifier(exception, resultClass, result)
138
+ }
139
+ result
107
140
}
108
141
}
109
142
@@ -116,16 +149,21 @@ object ExceptionMappersStorage {
116
149
*/
117
150
inline fun <E : Throwable , reified T : Any > find (throwable : E ): ((E ) -> T )? = find(
118
151
resultClass = T ::class ,
119
- throwable = throwable,
120
- exceptionClass = throwable::class
152
+ throwable = throwable
121
153
)
122
154
123
155
/* *
124
156
* Sets fallback (default) value for [T] errors type.
125
157
*/
126
158
fun <T : Any > setFallbackValue (clazz : KClass <T >, value : T ): ExceptionMappersStorage {
127
- fallbackValuesMap[clazz] = value
128
- return ExceptionMappersStorage
159
+ updateContainer(
160
+ clazz
161
+ ) { container ->
162
+ container.copy(
163
+ fallback = { value }
164
+ )
165
+ }
166
+ return this
129
167
}
130
168
131
169
/* *
@@ -135,37 +173,51 @@ object ExceptionMappersStorage {
135
173
setFallbackValue(T ::class , value)
136
174
137
175
/* *
138
- * Returns fallback (default) value for [T] errors type.
139
- * If there is no default value for the class [T], then [FallbackValueNotFoundException]
140
- * exception will be thrown.
176
+ * Sets fallback (default) factory for [T] errors type.
141
177
*/
142
- fun <T : Any > getFallbackValue (clazz : KClass <T >): T {
143
- @Suppress(" UNCHECKED_CAST" )
144
- return fallbackValuesMap[clazz] as ? T
145
- ? : throw FallbackValueNotFoundException (clazz)
178
+ fun <T : Any > setFallbackFactory (
179
+ clazz : KClass <T >,
180
+ factory : (Throwable ) -> T
181
+ ): ExceptionMappersStorage {
182
+ updateContainer(
183
+ clazz
184
+ ) { container ->
185
+ container.copy(
186
+ fallback = factory
187
+ )
188
+ }
189
+ return this
146
190
}
147
191
148
- /* *
149
- * Returns fallback (default) value for [T] errors type.
150
- * If there is no default value for the class [T], then [FallbackValueNotFoundException]
151
- * exception will be thrown.
152
- */
153
- inline fun <reified T : Any > getFallbackValue (): T = getFallbackValue(T ::class )
192
+ inline fun <reified T : Any > setFallbackFactory (
193
+ noinline factory : (Throwable ) -> T
194
+ ): ExceptionMappersStorage = setFallbackFactory(T ::class , factory)
154
195
155
196
/* *
156
197
* Factory method that creates mappers (Throwable) -> T with a registered fallback value for
157
198
* class [T].
158
199
*/
159
200
fun <E : Throwable , T : Any > throwableMapper (clazz : KClass <T >): (e: E ) -> T {
160
- val fallback = getFallbackValue(clazz)
161
201
return { e ->
162
- find(clazz, e, e:: class )?.invoke(e) ? : fallback
202
+ find(clazz, e)?.invoke(e) ? : throw FallbackValueNotFoundException (clazz)
163
203
}
164
204
}
165
205
166
206
inline fun <E : Throwable , reified T : Any > throwableMapper (): (e: E ) -> T {
167
207
return dev.icerock.moko.errors.mappers.throwableMapper()
168
208
}
209
+
210
+ /* *
211
+ * Listen all mappers calls. Useful for logging
212
+ *
213
+ * @param block - lambda that will be called when exception map to some class
214
+ */
215
+ fun onEach (
216
+ block : (Throwable , KClass <* >, Any ) -> Unit
217
+ ): ExceptionMappersStorage {
218
+ notifiers.add(block)
219
+ return this
220
+ }
169
221
}
170
222
171
223
/* *
0 commit comments