Created
April 6, 2017 20:17
-
-
Save ZakTaccardi/cbf5dfb257463fb5c3ae43261507bdc1 to your computer and use it in GitHub Desktop.
API Design — Handling exceptions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class BetterApi { | |
fun login(credentials: Credentials): LoginResponse { | |
//login... | |
TODO() | |
} | |
class LoginResponse private constructor( | |
val user: User?, | |
val error: Error? | |
) { | |
constructor(user: User) : this(user = user, error = null) | |
constructor(error: Error) : this(user = null, error = error) | |
init { | |
if (user == null && error == null) { | |
throw IllegalStateException("both user & error cannot be null") | |
} | |
if (user != null && error != null) { | |
throw IllegalStateException("either user or error must be null") | |
} | |
} | |
val isSuccessful = user != null | |
abstract class Error | |
object InvalidCredentials : Error() | |
object NoInternet : Error() | |
class Unknown(val throwable: Throwable) : Error() | |
} | |
private fun BetterApiUsage() { | |
val api = BetterApi() | |
val credentials = Credentials(username = "Zak", password = "designForErrorsReportDefects") | |
val response: LoginResponse = api.login(credentials) | |
if (response.isSuccessful) { | |
//handle successful login | |
} else { | |
val error: LoginResponse.Error = response.error!! | |
//handle each of the three possible error cases | |
error.handle( | |
{ invalidCredentials: LoginResponse.InvalidCredentials -> TODO() }, | |
{ noInternet: LoginResponse.NoInternet -> TODO() }, | |
{ unknown: LoginResponse.Unknown -> TODO() } | |
) | |
} | |
fun observe(): Observable<LoginResponse> = Observable.just(response) | |
fun observeSuccessful(): Observable<User> { | |
return observe() | |
.filter { response: BetterApi.LoginResponse -> response.isSuccessful } | |
.map { response: BetterApi.LoginResponse -> response.user!! } | |
} | |
fun observeErrors(): Observable<BetterApi.LoginResponse.Error> { | |
return observe() | |
.filter { response: BetterApi.LoginResponse -> !response.isSuccessful } | |
.map { response -> response.error!! } | |
} | |
observeSuccessful().subscribe { user: User -> | |
//handle successful login | |
} | |
observeErrors().subscribe { error: BetterApi.LoginResponse.Error -> | |
//handle different types of errors | |
error.handle( | |
{ invalidCredentials -> TODO() }, | |
{ noInternet -> TODO() }, | |
{ unknown -> TODO() } | |
) | |
} | |
} | |
private fun BetterApi.LoginResponse.Error.handle( | |
invalidCredentials: (BetterApi.LoginResponse.InvalidCredentials) -> Nothing, | |
noInternet: (BetterApi.LoginResponse.NoInternet) -> Nothing, | |
unknown: (BetterApi.LoginResponse.Unknown) -> Nothing | |
) { | |
if (this is BetterApi.LoginResponse.Error) { | |
invalidCredentials.invoke(this as BetterApi.LoginResponse.InvalidCredentials) | |
} else if (this is BetterApi.LoginResponse.NoInternet) { | |
noInternet.invoke((this as BetterApi.LoginResponse.NoInternet)) | |
} else if (this is BetterApi.LoginResponse.Unknown) { | |
unknown.invoke((this as BetterApi.LoginResponse.Unknown)) | |
} else { | |
throw AssertionError("you did not implement all possible error scenarios") | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class KotlinApi { | |
fun login(credentials: Credentials): LoginResponse { | |
//login... | |
TODO() | |
} | |
sealed class LoginResponse { | |
data class Success(val user: User) : LoginResponse() | |
data class Error(val error: LoginError) : LoginResponse() | |
} | |
sealed class LoginError { | |
object InvalidCredentials : LoginError() | |
object NoInternet : LoginError() | |
class Unknown(throwable: Throwable) : LoginError() | |
} | |
private fun kotlinApiUsage() { | |
val api = KotlinApi() | |
val credentials = Credentials(username = "Zak", password = "designForErrorsReportDefects") | |
val response: LoginResponse = api.login(credentials) | |
when (response) { | |
is LoginResponse.Success -> { | |
//handle success | |
} | |
is Error -> when (response.error) { | |
is LoginError.InvalidCredentials -> { | |
//handle invalid credentials | |
} | |
is LoginError.NoInternet -> { | |
//handle lack of connectivity | |
} | |
is LoginError.Unknown -> { | |
//report defect! | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NaiveApi { | |
fun login(credentials: Credentials): User { | |
TODO() //login... | |
} | |
} | |
fun naiveApiUsage() { | |
val api = NaiveApi() | |
val credentials = Credentials( | |
username = "Zak", | |
password = "designForErrorsReportDefects" | |
) | |
val user = api.login(credentials) | |
println("Logged in ${user.username}") | |
user.firstName | |
} | |
data class User(val username: String, val firstName: String, val lastName: String) | |
data class Credentials(val username: String, val password: String) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment