Skip to content

Commit a468feb

Browse files
committed
#25 fallback factory implemented
1 parent 3def8a4 commit a468feb

File tree

4 files changed

+102
-63
lines changed

4 files changed

+102
-63
lines changed

errors/src/commonMain/kotlin/dev/icerock/moko/errors/mappers/ConditionPair.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

errors/src/commonMain/kotlin/dev/icerock/moko/errors/mappers/ExceptionMappersStorage.kt

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,34 @@ import dev.icerock.moko.resources.desc.desc
1010
import kotlin.native.concurrent.ThreadLocal
1111
import kotlin.reflect.KClass
1212

13-
internal typealias ThrowableMapper = (Throwable) -> Any
14-
1513
@Suppress("TooManyFunctions")
1614
@ThreadLocal
1715
object 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
)
2223

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()
24+
private fun <T : Any> getOrCreateContainer(resultClass: KClass<T>): MappersContainer<T> {
25+
val existContainer: MappersContainer<*>? = containers[resultClass]
26+
if (existContainer != null) return existContainer as MappersContainer<T>
27+
28+
return MappersContainer<T>(
29+
mappers = emptyList(),
30+
fallback = { throw FallbackValueNotFoundException(resultClass) }
31+
).also { containers[resultClass] = it }
32+
}
33+
34+
private fun <T : Any> updateContainer(
35+
resultClass: KClass<T>,
36+
block: (MappersContainer<T>) -> MappersContainer<T>
37+
) {
38+
val container: MappersContainer<T> = getOrCreateContainer(resultClass)
39+
containers[resultClass] = block(container)
40+
}
2741

2842
/**
2943
* Register simple mapper (E) -> T.
@@ -33,11 +47,16 @@ object ExceptionMappersStorage {
3347
exceptionClass: KClass<E>,
3448
mapper: (E) -> T
3549
): ExceptionMappersStorage {
36-
if (!mappersMap.containsKey(resultClass)) {
37-
mappersMap[resultClass] = mutableMapOf()
50+
updateContainer(
51+
resultClass
52+
) { container ->
53+
container.copy(
54+
mappers = container.mappers + ThrowableMapperItem(
55+
mapper = { mapper(it as E) },
56+
isApplied = { it::class == exceptionClass }
57+
)
58+
)
3859
}
39-
@Suppress("UNCHECKED_CAST")
40-
mappersMap[resultClass]?.put(exceptionClass, mapper as ThrowableMapper)
4160
return this
4261
}
4362

@@ -46,12 +65,19 @@ object ExceptionMappersStorage {
4665
*/
4766
fun <T : Any> register(
4867
resultClass: KClass<T>,
49-
conditionPair: ConditionPair
68+
isApplied: (Throwable) -> Boolean,
69+
mapper: (Throwable) -> T
5070
): ExceptionMappersStorage {
51-
if (!conditionMappers.containsKey(resultClass)) {
52-
conditionMappers[resultClass] = mutableListOf()
71+
updateContainer(
72+
resultClass
73+
) { container ->
74+
container.copy(
75+
mappers = container.mappers + ThrowableMapperItem(
76+
mapper = mapper,
77+
isApplied = isApplied
78+
)
79+
)
5380
}
54-
conditionMappers[resultClass]?.add(conditionPair)
5581
return this
5682
}
5783

@@ -76,10 +102,8 @@ object ExceptionMappersStorage {
76102
noinline mapper: (Throwable) -> T
77103
): ExceptionMappersStorage = register(
78104
resultClass = T::class,
79-
conditionPair = ConditionPair(
80-
condition,
81-
mapper as ThrowableMapper
82-
)
105+
isApplied = condition,
106+
mapper = mapper
83107
)
84108

85109
/**
@@ -91,20 +115,20 @@ object ExceptionMappersStorage {
91115
*/
92116
fun <E : Throwable, T : Any> find(
93117
resultClass: KClass<T>,
94-
throwable: E,
95-
exceptionClass: KClass<out E>
118+
throwable: E
96119
): ((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)
120+
val container: MappersContainer<T>? = containers[resultClass] as MappersContainer<T>?
102121

103-
return if (mapper == null && throwable !is Exception) {
122+
if (container == null && throwable !is Exception) {
104123
throw throwable
105-
} else {
106-
mapper
124+
} else if (container == null) {
125+
return null
107126
}
127+
128+
return container.mappers
129+
.firstOrNull { it.isApplied(throwable) }
130+
?.mapper
131+
?: container.fallback
108132
}
109133

110134
/**
@@ -116,16 +140,21 @@ object ExceptionMappersStorage {
116140
*/
117141
inline fun <E : Throwable, reified T : Any> find(throwable: E): ((E) -> T)? = find(
118142
resultClass = T::class,
119-
throwable = throwable,
120-
exceptionClass = throwable::class
143+
throwable = throwable
121144
)
122145

123146
/**
124147
* Sets fallback (default) value for [T] errors type.
125148
*/
126149
fun <T : Any> setFallbackValue(clazz: KClass<T>, value: T): ExceptionMappersStorage {
127-
fallbackValuesMap[clazz] = value
128-
return ExceptionMappersStorage
150+
updateContainer(
151+
clazz
152+
) { container ->
153+
container.copy(
154+
fallback = { value }
155+
)
156+
}
157+
return this
129158
}
130159

131160
/**
@@ -135,37 +164,35 @@ object ExceptionMappersStorage {
135164
setFallbackValue(T::class, value)
136165

137166
/**
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.
167+
* Sets fallback (default) factory for [T] errors type.
141168
*/
142-
fun <T : Any> getFallbackValue(clazz: KClass<T>): T {
143-
@Suppress("UNCHECKED_CAST")
144-
return fallbackValuesMap[clazz] as? T
145-
?: throw FallbackValueNotFoundException(clazz)
169+
fun <T : Any> setFallbackFactory(
170+
clazz: KClass<T>,
171+
factory: (Throwable) -> T
172+
): ExceptionMappersStorage {
173+
updateContainer(
174+
clazz
175+
) { container ->
176+
container.copy(
177+
fallback = factory
178+
)
179+
}
180+
return this
146181
}
147182

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)
183+
inline fun <reified T : Any> setFallbackFactory(
184+
noinline factory: (Throwable) -> T
185+
): ExceptionMappersStorage = setFallbackFactory(T::class, factory)
154186

155187
/**
156188
* Factory method that creates mappers (Throwable) -> T with a registered fallback value for
157189
* class [T].
158190
*/
159191
fun <E : Throwable, T : Any> throwableMapper(clazz: KClass<T>): (e: E) -> T {
160-
val fallback = getFallbackValue(clazz)
161192
return { e ->
162-
find(clazz, e, e::class)?.invoke(e) ?: fallback
193+
find(clazz, e)?.invoke(e) ?: throw FallbackValueNotFoundException(clazz)
163194
}
164195
}
165-
166-
inline fun <E : Throwable, reified T : Any> throwableMapper(): (e: E) -> T {
167-
return dev.icerock.moko.errors.mappers.throwableMapper()
168-
}
169196
}
170197

171198
/**
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.errors.mappers
6+
7+
internal data class MappersContainer<T>(
8+
val mappers: List<ThrowableMapperItem<T>>,
9+
val fallback: ThrowableMapper<T>
10+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright 2023 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.errors.mappers
6+
7+
internal typealias ThrowableMapper<T> = (Throwable) -> T
8+
9+
internal data class ThrowableMapperItem<T>(
10+
val mapper: ThrowableMapper<T>,
11+
val isApplied: (Throwable) -> Boolean
12+
)

0 commit comments

Comments
 (0)