feat(characters:data): DTOs, mappers, data source, repo, Koin module (REDI-86)
- @Serializable CharacterDto/CharactersResponseDto/PageInfoDto in dto/.
- mappers/CharacterMapper.kt: internal, pure toDomain()/toCharacter()/toCharacterDetails();
nextPage parsed from info.next URL. No mapping inside DTO/data-source classes.
- KtorCharacterDataSource via the typed HttpClient.get helpers (errors -> DataError.Network).
- NetworkCharacterRepository (not *Impl) maps DTO -> domain; DataError.Network widens to DataError.
- charactersDataModule: singleOf(::KtorCharacterDataSource) + singleOf(::NetworkCharacterRepository) { bind<CharacterRepository>() }.
- core:data: expose ktor-client-core as api (public inline helpers are inlined into consumers) and
move Timber logging into a @PublishedApi internal fn so Timber doesn't leak across modules.
This commit is contained in:
@@ -19,4 +19,7 @@ android {
|
||||
dependencies {
|
||||
implementation(project(":core:domain"))
|
||||
implementation(libs.timber)
|
||||
// `api`: the public inline HttpClient.get/post/delete helpers are inlined into consumer modules,
|
||||
// so those modules need the Ktor request/response types on their compile classpath.
|
||||
api(libs.ktor.client.core)
|
||||
}
|
||||
|
||||
@@ -65,21 +65,31 @@ suspend inline fun <reified T> safeCall(
|
||||
return try {
|
||||
responseToResult(execute())
|
||||
} catch (e: UnresolvedAddressException) {
|
||||
Timber.tag("HttpClient").e(e, "No internet (unresolved address)")
|
||||
logNetworkError(e, "No internet (unresolved address)")
|
||||
Result.Error(DataError.Network.NO_INTERNET)
|
||||
} catch (e: UnknownHostException) {
|
||||
Timber.tag("HttpClient").e(e, "No internet (unknown host)")
|
||||
logNetworkError(e, "No internet (unknown host)")
|
||||
Result.Error(DataError.Network.NO_INTERNET)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.tag("HttpClient").e(e, "Serialization failure")
|
||||
logNetworkError(e, "Serialization failure")
|
||||
Result.Error(DataError.Network.SERIALIZATION)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Timber.tag("HttpClient").e(e, "Unknown network failure")
|
||||
logNetworkError(e, "Unknown network failure")
|
||||
Result.Error(DataError.Network.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a caught network error. `@PublishedApi internal` so the public inline [safeCall] can call it
|
||||
* across modules WITHOUT leaking Timber: the Timber dependency stays inside `:core:data` because
|
||||
* this function's body is not inlined into the caller.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun logNetworkError(throwable: Throwable, message: String) {
|
||||
Timber.tag("HttpClient").e(throwable, message)
|
||||
}
|
||||
|
||||
/** Maps HTTP status codes to typed [DataError.Network] (extends the skill table with 400/403/404). */
|
||||
suspend inline fun <reified T> responseToResult(
|
||||
response: HttpResponse,
|
||||
|
||||
Reference in New Issue
Block a user