Fix api error parses always in generic error.

Now it parses to right error representation. Also fix that right
exception for error is not thrown on api call.

Signed-off-by: Yahor Berdnikau <egorr.berd@gmail.com>
This commit is contained in:
Yahor Berdnikau 2018-01-13 10:19:42 +01:00
parent fa03cf3881
commit 3e1dbe3476
24 changed files with 157 additions and 116 deletions

View File

@ -59,14 +59,14 @@ fun parseDate(dateAsString: String): Calendar {
fun <T : SubsonicResponse> checkErrorCallParsed(mockWebServerRule: MockWebServerRule,
apiRequest: () -> Response<T>): T {
mockWebServerRule.enqueueResponse("generic_error_response.json")
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
val response = apiRequest()
assertResponseSuccessful(response)
with(response.body()) {
status `should be` SubsonicResponse.Status.ERROR
error `should be` SubsonicError.GENERIC
error `should be` SubsonicError.RequestedDataWasNotFound
}
return response.body()
}

View File

@ -0,0 +1,51 @@
package org.moire.ultrasonic.api.subsonic
import org.amshove.kluent.`should equal`
import org.amshove.kluent.`should not be`
import org.amshove.kluent.`should throw`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.SubsonicError.Generic
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
import retrofit2.Response
import java.io.IOException
/**
* Integration test that checks validity of api errors parsing.
*/
class SubsonicApiErrorsTest : SubsonicAPIClientTest() {
@Test
fun `Should parse wrong username or password error`() {
mockWebServerRule.enqueueResponse("wrong_username_or_password_error.json")
val response = client.api.ping().execute()
response.assertError(WrongUsernameOrPassword)
}
@Test
fun `Should parse generic error with message`() {
mockWebServerRule.enqueueResponse("generic_error.json")
val response = client.api.ping().execute()
response.assertError(Generic("Some generic error message."))
}
@Test
fun `Should fail on unknown error`() {
mockWebServerRule.enqueueResponse("unexpected_error.json")
val fail = {
client.api.ping().execute()
}
fail `should throw` IOException::class
}
private fun Response<SubsonicResponse>.assertError(expectedError: SubsonicError) =
with(body()) {
error `should not be` null
error `should equal` expectedError
}
}

View File

@ -13,14 +13,14 @@ import org.junit.Test
class SubsonicApiGetAvatarTest : SubsonicAPIClientTest() {
@Test
fun `Should handle api error response`() {
mockWebServerRule.enqueueResponse("generic_error_response.json")
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
val response = client.getAvatar("some")
with(response) {
stream `should be` null
responseHttpCode `should equal to` 200
apiError `should equal` SubsonicError.GENERIC
apiError `should equal` SubsonicError.RequestedDataWasNotFound
}
}

View File

@ -13,14 +13,14 @@ import org.junit.Test
class SubsonicApiGetCoverArtTest : SubsonicAPIClientTest() {
@Test
fun `Should handle api error response`() {
mockWebServerRule.enqueueResponse("generic_error_response.json")
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
val response = client.getCoverArt("some-id")
with(response) {
stream `should be` null
responseHttpCode `should equal to` 200
apiError `should equal` SubsonicError.GENERIC
apiError `should equal` SubsonicError.RequestedDataWasNotFound
}
}

View File

@ -13,14 +13,14 @@ import org.junit.Test
class SubsonicApiStreamTest : SubsonicAPIClientTest() {
@Test
fun `Should handle api error response`() {
mockWebServerRule.enqueueResponse("generic_error_response.json")
mockWebServerRule.enqueueResponse("request_data_not_found_error_response.json")
val response = client.stream("some-id")
with(response) {
stream `should be` null
responseHttpCode `should equal to` 200
apiError `should equal` SubsonicError.GENERIC
apiError `should equal` SubsonicError.RequestedDataWasNotFound
}
}

View File

@ -0,0 +1,10 @@
{
"subsonic-response": {
"status": "failed",
"version": "1.15.0",
"error": {
"code": 0,
"message": "Some generic error message."
}
}
}

View File

@ -3,8 +3,8 @@
"status" : "failed",
"version" : "1.13.0",
"error" : {
"code" : 0,
"message" : "Generic error."
"code" : 70,
"message" : "Requested data was not found."
}
}
}

View File

@ -0,0 +1,10 @@
{
"subsonic-response": {
"status": "failed",
"version": "1.15.0",
"error": {
"code": 1000000,
"message": "New funky error message."
}
}
}

View File

@ -0,0 +1,10 @@
{
"subsonic-response": {
"status": "failed",
"version": "1.15.0",
"error": {
"code": 40,
"message": "Wrong username or password."
}
}
}

View File

@ -9,29 +9,41 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
* Common API errors.
*/
@JsonDeserialize(using = SubsonicError.Companion.SubsonicErrorDeserializer::class)
enum class SubsonicError(val code: Int) {
GENERIC(0),
REQUIRED_PARAM_MISSING(10),
INCOMPATIBLE_CLIENT_PROTOCOL_VERSION(20),
INCOMPATIBLE_SERVER_PROTOCOL_VERSION(30),
WRONG_USERNAME_OR_PASSWORD(40),
TOKEN_AUTH_NOT_SUPPORTED_FOR_LDAP(41),
USER_NOT_AUTHORIZED_FOR_OPERATION(50),
TRIAL_PERIOD_IS_OVER(60),
REQUESTED_DATA_WAS_NOT_FOUND(70);
sealed class SubsonicError(val code: Int) {
data class Generic(val message: String) : SubsonicError(0)
object RequiredParamMissing : SubsonicError(10)
object IncompatibleClientProtocolVersion : SubsonicError(20)
object IncompatibleServerProtocolVersion : SubsonicError(30)
object WrongUsernameOrPassword : SubsonicError(40)
object TokenAuthNotSupportedForLDAP : SubsonicError(41)
object UserNotAuthorizedForOperation : SubsonicError(50)
object TrialPeriodIsOver : SubsonicError(60)
object RequestedDataWasNotFound : SubsonicError(70)
companion object {
fun parseErrorFromJson(jsonErrorCode: Int) = SubsonicError.values()
.filter { it.code == jsonErrorCode }.firstOrNull()
?: throw IllegalArgumentException("Unknown code $jsonErrorCode")
fun getError(code: Int, message: String) = when (code) {
0 -> Generic(message)
10 -> RequiredParamMissing
20 -> IncompatibleClientProtocolVersion
30 -> IncompatibleServerProtocolVersion
40 -> WrongUsernameOrPassword
41 -> TokenAuthNotSupportedForLDAP
50 -> UserNotAuthorizedForOperation
60 -> TrialPeriodIsOver
70 -> RequestedDataWasNotFound
else -> throw IllegalArgumentException("Unknown code $code")
}
class SubsonicErrorDeserializer : JsonDeserializer<SubsonicError>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicError {
p.nextToken() // "code"
val error = parseErrorFromJson(p.valueAsInt)
p.nextToken() // "message"
p.nextToken() // end of error object
return error
p.nextToken() // { -> "code"
p.nextToken() // "code" -> codeValue
val code = p.valueAsInt
p.nextToken() // codeValue -> "message"
p.nextToken() // "message" -> messageValue
val message = p.text
p.nextToken() // value -> }
return getError(code, message)
}
}
}

View File

@ -1,28 +0,0 @@
package org.moire.ultrasonic.api.subsonic
import org.amshove.kluent.`should equal`
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
* Unit test for [SubsonicError].
*/
@RunWith(Parameterized::class)
class SubsonicErrorTest(private val error: SubsonicError) {
companion object {
@JvmStatic
@Parameterized.Parameters
fun data(): List<SubsonicError> = SubsonicError.values().toList()
}
@Test
fun `Should proper convert error code to error`() {
SubsonicError.parseErrorFromJson(error.code) `should equal` error
}
@Test(expected = IllegalArgumentException::class)
fun `Should throw IllegalArgumentException from unknown error code`() {
SubsonicError.parseErrorFromJson(error.code + 10000)
}
}

View File

@ -2,7 +2,7 @@ package org.moire.ultrasonic.api.subsonic.response
import org.amshove.kluent.`should equal to`
import org.junit.Test
import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
/**
* Unit test for [StreamResponse].
@ -10,7 +10,8 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
class StreamResponseTest {
@Test
fun `Should have error if subsonic error is not null`() {
StreamResponse(apiError = GENERIC, responseHttpCode = 200).hasError() `should equal to` true
StreamResponse(apiError = RequestedDataWasNotFound, responseHttpCode = 200)
.hasError() `should equal to` true
}
@Test

View File

@ -31,7 +31,6 @@ import org.moire.ultrasonic.R;
import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException;
import org.moire.ultrasonic.domain.JukeboxStatus;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
import org.moire.ultrasonic.util.Util;
import java.util.ArrayList;

View File

@ -89,7 +89,6 @@ import org.moire.ultrasonic.domain.SearchCriteria;
import org.moire.ultrasonic.domain.SearchResult;
import org.moire.ultrasonic.domain.Share;
import org.moire.ultrasonic.domain.UserInfo;
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
import org.moire.ultrasonic.util.CancellableTask;
import org.moire.ultrasonic.util.FileUtil;
import org.moire.ultrasonic.util.ProgressListener;
@ -1077,7 +1076,7 @@ public class RESTMusicService implements MusicService {
}
private void checkResponseSuccessful(@NonNull final Response<? extends SubsonicResponse> response)
throws IOException {
throws SubsonicRESTException, IOException {
if (response.isSuccessful() &&
response.body().getStatus() == SubsonicResponse.Status.OK) {
return;
@ -1087,7 +1086,7 @@ public class RESTMusicService implements MusicService {
throw new IOException("Server error, code: " + response.code());
} else if (response.body().getStatus() == SubsonicResponse.Status.ERROR &&
response.body().getError() != null) {
throw new IOException("Server error: " + response.body().getError().getCode());
throw new SubsonicRESTException(response.body().getError());
} else {
throw new IOException("Failed to perform request: " + response.code());
}

View File

@ -1,27 +0,0 @@
package org.moire.ultrasonic.service.parser;
import org.moire.ultrasonic.api.subsonic.SubsonicError;
/**
* Exception returned by API with given {@code code}.
*
* @author Sindre Mehus
* @version $Id$
*/
public class SubsonicRESTException extends Exception {
private final SubsonicError error;
public SubsonicRESTException(final SubsonicError error) {
super("Api error: " + error.name());
this.error = error;
}
public int getCode()
{
return error.getCode();
}
public SubsonicError getError() {
return error;
}
}

View File

@ -25,7 +25,7 @@ import android.util.Log;
import com.fasterxml.jackson.core.JsonParseException;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.service.parser.SubsonicRESTException;
import org.moire.ultrasonic.service.SubsonicRESTException;
import org.moire.ultrasonic.subsonic.RestErrorMapper;
import java.io.FileNotFoundException;

View File

@ -0,0 +1,10 @@
package org.moire.ultrasonic.service
import org.moire.ultrasonic.api.subsonic.SubsonicError
/**
* Exception returned by API with given `code`.
*/
class SubsonicRESTException(val error: SubsonicError) : Exception("Api error: ${error.code}") {
val code: Int get() = error.code
}

View File

@ -3,16 +3,16 @@ package org.moire.ultrasonic.subsonic
import android.content.Context
import org.moire.ultrasonic.R
import org.moire.ultrasonic.api.subsonic.SubsonicError.GENERIC
import org.moire.ultrasonic.api.subsonic.SubsonicError.INCOMPATIBLE_CLIENT_PROTOCOL_VERSION
import org.moire.ultrasonic.api.subsonic.SubsonicError.INCOMPATIBLE_SERVER_PROTOCOL_VERSION
import org.moire.ultrasonic.api.subsonic.SubsonicError.REQUESTED_DATA_WAS_NOT_FOUND
import org.moire.ultrasonic.api.subsonic.SubsonicError.REQUIRED_PARAM_MISSING
import org.moire.ultrasonic.api.subsonic.SubsonicError.TOKEN_AUTH_NOT_SUPPORTED_FOR_LDAP
import org.moire.ultrasonic.api.subsonic.SubsonicError.TRIAL_PERIOD_IS_OVER
import org.moire.ultrasonic.api.subsonic.SubsonicError.USER_NOT_AUTHORIZED_FOR_OPERATION
import org.moire.ultrasonic.api.subsonic.SubsonicError.WRONG_USERNAME_OR_PASSWORD
import org.moire.ultrasonic.service.parser.SubsonicRESTException
import org.moire.ultrasonic.api.subsonic.SubsonicError.Generic
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleClientProtocolVersion
import org.moire.ultrasonic.api.subsonic.SubsonicError.IncompatibleServerProtocolVersion
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequestedDataWasNotFound
import org.moire.ultrasonic.api.subsonic.SubsonicError.RequiredParamMissing
import org.moire.ultrasonic.api.subsonic.SubsonicError.TokenAuthNotSupportedForLDAP
import org.moire.ultrasonic.api.subsonic.SubsonicError.TrialPeriodIsOver
import org.moire.ultrasonic.api.subsonic.SubsonicError.UserNotAuthorizedForOperation
import org.moire.ultrasonic.api.subsonic.SubsonicError.WrongUsernameOrPassword
import org.moire.ultrasonic.service.SubsonicRESTException
/**
* Extension for [SubsonicRESTException] that returns localized error string, that can used to
@ -20,19 +20,19 @@ import org.moire.ultrasonic.service.parser.SubsonicRESTException
*/
fun SubsonicRESTException.getLocalizedErrorMessage(context: Context): String =
when (error) {
GENERIC -> context.getString(R.string.api_subsonic_generic, message)
REQUIRED_PARAM_MISSING -> context.getString(R.string.api_subsonic_param_missing)
INCOMPATIBLE_CLIENT_PROTOCOL_VERSION -> context
is Generic -> context
.getString(R.string.api_subsonic_generic, (error as Generic).message)
RequiredParamMissing -> context.getString(R.string.api_subsonic_param_missing)
IncompatibleClientProtocolVersion -> context
.getString(R.string.api_subsonic_upgrade_client)
INCOMPATIBLE_SERVER_PROTOCOL_VERSION -> context
IncompatibleServerProtocolVersion -> context
.getString(R.string.api_subsonic_upgrade_server)
WRONG_USERNAME_OR_PASSWORD -> context.getString(R.string.api_subsonic_not_authenticated)
TOKEN_AUTH_NOT_SUPPORTED_FOR_LDAP -> context
WrongUsernameOrPassword -> context.getString(R.string.api_subsonic_not_authenticated)
TokenAuthNotSupportedForLDAP -> context
.getString(R.string.api_subsonic_token_auth_not_supported_for_ldap)
USER_NOT_AUTHORIZED_FOR_OPERATION -> context
UserNotAuthorizedForOperation -> context
.getString(R.string.api_subsonic_not_authorized)
TRIAL_PERIOD_IS_OVER -> context.getString(R.string.api_subsonic_trial_period_is_over)
REQUESTED_DATA_WAS_NOT_FOUND -> context
TrialPeriodIsOver -> context.getString(R.string.api_subsonic_trial_period_is_over)
RequestedDataWasNotFound -> context
.getString(R.string.api_subsonic_requested_data_was_not_found)
else -> context.getString(R.string.api_subsonic_unknown_api_error)
}

View File

@ -430,7 +430,6 @@
<string name="api.subsonic.param_missing">Falta el parámetro requerido.</string>
<string name="api.subsonic.requested_data_was_not_found">No se encontraron los datos solicitados.</string>
<string name="api.subsonic.trial_period_is_over">El período de prueba ha terminado.</string>
<string name="api.subsonic.unknown_api_error">Error de api desconocido.</string>
<string name="api.subsonic.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android UltraSonic.</string>
<string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>

View File

@ -430,7 +430,6 @@
<string name="api.subsonic.param_missing">Param nécessaire manquant.</string>
<string name="api.subsonic.requested_data_was_not_found">Les données demandées n\'ont pas été trouvées.</string>
<string name="api.subsonic.trial_period_is_over">La période d\'essai est terminée.</string>
<string name="api.subsonic.unknown_api_error">Erreur d\'api inconnue.</string>
<string name="api.subsonic.upgrade_client">Versions incompatible. Veuillez mette à jour l\'application Android UltraSonic.</string>
<string name="api.subsonic.upgrade_server">Versions incompatible. Veuillez mette à jour le serveur Subsonic.</string>

View File

@ -430,7 +430,6 @@
<string name="api.subsonic.param_missing">A szükséges param hiányzik.</string>
<string name="api.subsonic.requested_data_was_not_found">A keresett adatokat nem találtuk.</string>
<string name="api.subsonic.trial_period_is_over">A próbaidő vége.</string>
<string name="api.subsonic.unknown_api_error">Ismeretlen api hiba.</string>
<string name="api.subsonic.upgrade_client">Nem kompatibilis verzió. Kérjük, frissítse az UltraSonic Android alkalmazást!</string>
<string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>

View File

@ -430,7 +430,6 @@
<string name="api.subsonic.param_missing">O parâmetro requerido está faltando.</string>
<string name="api.subsonic.requested_data_was_not_found">Os dados solicitados não foram encontrados.</string>
<string name="api.subsonic.trial_period_is_over">O período de avaliação acabou.</string>
<string name="api.subsonic.unknown_api_error">Erro de api desconhecido.</string>
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>

View File

@ -430,7 +430,6 @@
<string name="api.subsonic.param_missing">O parâmetro requerido está faltando.</string>
<string name="api.subsonic.requested_data_was_not_found">Os dados solicitados não foram encontrados.</string>
<string name="api.subsonic.trial_period_is_over">O período de avaliação acabou.</string>
<string name="api.subsonic.unknown_api_error">Erro de api desconhecido.</string>
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo UltraSonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor UltraSonic.</string>

View File

@ -432,7 +432,6 @@
<string name="api.subsonic.param_missing">Required param is missing.</string>
<string name="api.subsonic.requested_data_was_not_found">Requested data was not found.</string>
<string name="api.subsonic.trial_period_is_over">Trial period is over.</string>
<string name="api.subsonic.unknown_api_error">Unknown api error.</string>
<string name="api.subsonic.upgrade_client">Incompatible versions. Please upgrade UltraSonic Android app.</string>
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>