Iurysza
08/26/2022, 1:37 PM@Singleton
class AuthUseCase @Inject constructor(
private val networkDataSource: NetworkDataSource,
private val storage: AuthStorage,
) {
suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
val token = storage.getToken().bind()
ensure(token.expirationDate.isInTheFuture()) {
TokenExpired
}
}.handleErrorWith { error ->
when (error) {
is TokenNotFound,
TokenExpired,
-> fetchNewTokenAndSaveIt()
else -> error.left()
}
}
private suspend fun fetchNewTokenAndSaveIt(): Either<DomainError, Unit> = either {
networkDataSource.getAccessToken().bind()
}.flatMap { (accessToken, expiresIn) ->
catch {
AuthToken(
accessToken,
expirationDate = nowPlusMillis(expiresIn)
)
}.mapLeft { InvalidExpirationDate }
}.map { storage.putToken(it) }
}
Is the code above arrow
idiomatic?
Anything that can be improved or that is completely off?
Thanks!qohatpp
08/26/2022, 3:29 PM@Singleton
class AuthUseCase @Inject constructor(
private val networkDataSource: NetworkDataSource,
private val storage: AuthStorage,
) {
suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
storage.getToken()
.ensure({ TokenNotExpired }, { !token.expirationDate.isInTheFuture() })
.bind() //This should interrupt with a DomainError if the old token is still valid and it is not necessary to refresh
val (accessToken, expiresIn) = networkDataSource.getAccessToken().bind()
val newToken = buildNewToken(accessToken, expiresIn).bind()
storage.putToken(newToken) //Would be great if you can map this sideeffect in an Either as well and call .bind()
}
private fun buildNewToken(accessToken: String, expiresIn: WhatEverExpiresInTypeIs): Either<DomainError, AuthToken> =
Either.catch {
AuthToken(
accessToken,
expirationDate = nowPlusMillis(expiresIn)
)
}.mapLeft { InvalidExpirationDate }
}
You also can handle the refreshTokenIfNeeded()
errors doing an extension function like this
inline fun <reified A : Any> Either<DomainError, A>.handleErrors(): Unit =
when (this) {
is Either.Left -> println("Was not necessary refresh")
is Either.Right -> println("The token was refreshed")
}
so you could do:
suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
...
}.handleErrors()
qohatpp
08/26/2022, 3:33 PMstorage.getToken()
.ensure({ TokenNotExpired }, { !token.expirationDate.isInTheFuture() })
.bind()
Is oriented to the right side of the either.
I would do like that, because I can avoid that the bind()
interrupts the continuation by doing shift<R>
in the case you get a left
in the ensure
.Iurysza
08/26/2022, 9:03 PMqohatpp
08/26/2022, 9:38 PMTokenNotFound
when calling the storage.getToken()
I mean, you can play with all of these ways and even find a better way to do it. Good luck!!
@Singleton
class AuthUseCase @Inject constructor(
private val networkDataSource: NetworkDataSource,
private val storage: AuthStorage,
) {
suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
val token = storage.getToken().ensure({ TokenExpired }, { token.expirationDate.isInTheFuture() })
when(token) {
is Either.Left -> fetchNewToken().bind()
is Either.Right -> println("Token is still valid")
}
}
private suspend fun fetchNewToken(): Either<DomainError, Unit> {
val (accessToken, expiresIn) = networkDataSource.getAccessToken().bind()
val newToken = buildNewToken(accessToken, expiresIn).bind()
storage.putToken(newToken).bind()
}
private fun buildNewToken(accessToken: String, expiresIn: WhatEverExpiresInTypeIs): Either<DomainError, AuthToken> =
Either.catch {
AuthToken(
accessToken,
expirationDate = nowPlusMillis(expiresIn)
)
}.mapLeft { InvalidExpirationDate }
}