@@ -10,20 +10,35 @@ import dev.icerock.moko.resources.desc.desc
1010import kotlin.native.concurrent.ThreadLocal
1111import kotlin.reflect.KClass
1212
13- internal typealias ThrowableMapper = (Throwable ) -> Any
14-
1513@Suppress(" TooManyFunctions" )
1614@ThreadLocal
1715object ExceptionMappersStorage {
1816
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+ )
2122 )
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 >
2228
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+ }
2742
2843 /* *
2944 * Register simple mapper (E) -> T.
@@ -33,11 +48,16 @@ object ExceptionMappersStorage {
3348 exceptionClass : KClass <E >,
3449 mapper : (E ) -> T
3550 ): 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+ )
3860 }
39- @Suppress(" UNCHECKED_CAST" )
40- mappersMap[resultClass]?.put(exceptionClass, mapper as ThrowableMapper )
4161 return this
4262 }
4363
@@ -46,12 +66,19 @@ object ExceptionMappersStorage {
4666 */
4767 fun <T : Any > register (
4868 resultClass : KClass <T >,
49- conditionPair : ConditionPair
69+ isApplied : (Throwable ) -> Boolean ,
70+ mapper : (Throwable ) -> T
5071 ): 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+ )
5381 }
54- conditionMappers[resultClass]?.add(conditionPair)
5582 return this
5683 }
5784
@@ -76,10 +103,8 @@ object ExceptionMappersStorage {
76103 noinline mapper : (Throwable ) -> T
77104 ): ExceptionMappersStorage = register(
78105 resultClass = T ::class ,
79- conditionPair = ConditionPair (
80- condition,
81- mapper as ThrowableMapper
82- )
106+ isApplied = condition,
107+ mapper = mapper
83108 )
84109
85110 /* *
@@ -91,19 +116,27 @@ object ExceptionMappersStorage {
91116 */
92117 fun <E : Throwable , T : Any > find (
93118 resultClass : KClass <T >,
94- throwable : E ,
95- exceptionClass : KClass <out E >
119+ throwable : E
96120 ): ((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 >?
102122
103- return if (mapper == null && throwable !is Exception ) {
123+ if (container == null && throwable !is Exception ) {
104124 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
107140 }
108141 }
109142
@@ -116,16 +149,21 @@ object ExceptionMappersStorage {
116149 */
117150 inline fun <E : Throwable , reified T : Any > find (throwable : E ): ((E ) -> T )? = find(
118151 resultClass = T ::class ,
119- throwable = throwable,
120- exceptionClass = throwable::class
152+ throwable = throwable
121153 )
122154
123155 /* *
124156 * Sets fallback (default) value for [T] errors type.
125157 */
126158 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
129167 }
130168
131169 /* *
@@ -135,36 +173,46 @@ object ExceptionMappersStorage {
135173 setFallbackValue(T ::class , value)
136174
137175 /* *
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.
141177 */
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
146190 }
147191
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)
154195
155196 /* *
156197 * Factory method that creates mappers (Throwable) -> T with a registered fallback value for
157198 * class [T].
158199 */
159200 fun <E : Throwable , T : Any > throwableMapper (clazz : KClass <T >): (e: E ) -> T {
160- val fallback = getFallbackValue(clazz)
161201 return { e ->
162- find(clazz, e, e:: class )?.invoke(e) ? : fallback
202+ find(clazz, e)?.invoke(e) ? : throw FallbackValueNotFoundException (clazz)
163203 }
164204 }
165205
166- inline fun <E : Throwable , reified T : Any > throwableMapper (): (e: E ) -> T {
167- return dev.icerock.moko.errors.mappers.throwableMapper()
206+ /* *
207+ * Listen all mappers calls. Useful for logging
208+ *
209+ * @param block - lambda that will be called when exception map to some class
210+ */
211+ fun onEach (
212+ block : (Throwable , KClass <* >, Any ) -> Unit
213+ ): ExceptionMappersStorage {
214+ notifiers.add(block)
215+ return this
168216 }
169217}
170218
0 commit comments