Skip to content

Instantly share code, notes, and snippets.

@ZakTaccardi
Created April 6, 2017 20:17
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ZakTaccardi/cbf5dfb257463fb5c3ae43261507bdc1 to your computer and use it in GitHub Desktop.
Save ZakTaccardi/cbf5dfb257463fb5c3ae43261507bdc1 to your computer and use it in GitHub Desktop.
API Design — Handling exceptions
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")
}
}
}
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!
}
}
}
}
}
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