From 0b5cc5d3f3680ae1586b65b87ef7a8259e1220ab Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Sat, 20 Jan 2018 10:56:58 +0100 Subject: [PATCH] Make subsonic error parser more fail safe. Now it should parse more malformed error json, though having "code" field is still mandatory. Signed-off-by: Yahor Berdnikau --- .../api/subsonic/SubsonicApiErrorsTest.kt | 36 +++++++++++++++++++ .../resources/error_first_generic_error.json | 10 ++++++ .../reversed_tokens_generic_error.json | 10 ++++++ ..._additional_json_object_generic_error.json | 13 +++++++ .../without_message_generic_error.json | 9 +++++ .../ultrasonic/api/subsonic/SubsonicError.kt | 20 +++++++---- 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 subsonic-api/src/integrationTest/resources/error_first_generic_error.json create mode 100644 subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json create mode 100644 subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json create mode 100644 subsonic-api/src/integrationTest/resources/without_message_generic_error.json diff --git a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt index f91e4557..3b8c66f9 100644 --- a/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt +++ b/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicApiErrorsTest.kt @@ -113,6 +113,42 @@ class SubsonicApiErrorsTest : SubsonicAPIClientTest() { response.assertError(RequestedDataWasNotFound) } + @Test + fun `Should parse error with reversed tokens order`() { + mockWebServerRule.enqueueResponse("reversed_tokens_generic_error.json") + + val response = client.api.ping().execute() + + response.assertError(Generic("Video streaming not supported")) + } + + @Test + fun `Should parse error if json contains error first before other fields`() { + mockWebServerRule.enqueueResponse("error_first_generic_error.json") + + val response = client.api.ping().execute() + + response.assertError(Generic("Video streaming not supported")) + } + + @Test + fun `Should parse error if json doesn't contain message field`() { + mockWebServerRule.enqueueResponse("without_message_generic_error.json") + + val response = client.api.ping().execute() + + response.assertError(Generic("")) + } + + @Test + fun `Should parse error if error json contains additional object`() { + mockWebServerRule.enqueueResponse("with_additional_json_object_generic_error.json") + + val response = client.api.ping().execute() + + response.assertError(Generic("")) + } + private fun Response.assertError(expectedError: SubsonicError) = with(body()) { error `should not be` null diff --git a/subsonic-api/src/integrationTest/resources/error_first_generic_error.json b/subsonic-api/src/integrationTest/resources/error_first_generic_error.json new file mode 100644 index 00000000..a278910e --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/error_first_generic_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "error": { + "message": "Video streaming not supported", + "code": 0 + }, + "version": "1.8.0", + "status": "failed" + } +} diff --git a/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json b/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json new file mode 100644 index 00000000..549214c5 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/reversed_tokens_generic_error.json @@ -0,0 +1,10 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "message": "Video streaming not supported", + "code": 0 + } + } +} \ No newline at end of file diff --git a/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json b/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json new file mode 100644 index 00000000..5b44bb90 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/with_additional_json_object_generic_error.json @@ -0,0 +1,13 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "code": 0, + "unicorn" : { + "code": 41, + "message": "Unicorns doesn't exist!" + } + } + } +} diff --git a/subsonic-api/src/integrationTest/resources/without_message_generic_error.json b/subsonic-api/src/integrationTest/resources/without_message_generic_error.json new file mode 100644 index 00000000..904d1508 --- /dev/null +++ b/subsonic-api/src/integrationTest/resources/without_message_generic_error.json @@ -0,0 +1,9 @@ +{ + "subsonic-response": { + "status": "failed", + "version": "1.8.0", + "error": { + "code": 0 + } + } +} diff --git a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt index 89559cb2..ad1419e4 100644 --- a/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt +++ b/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt @@ -1,6 +1,8 @@ package org.moire.ultrasonic.api.subsonic import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken.END_OBJECT +import com.fasterxml.jackson.core.JsonToken.START_OBJECT import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.annotation.JsonDeserialize @@ -36,13 +38,17 @@ sealed class SubsonicError(val code: Int) { class SubsonicErrorDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): SubsonicError { - 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 -> } + var code = -1 + var message = "" + while (p.nextToken() != END_OBJECT) { + when { + p.currentToken == START_OBJECT -> p.skipChildren() + "code".equals(p.currentName, ignoreCase = true) -> + code = p.nextIntValue(-1) + "message".equals(p.currentName, ignoreCase = true) -> + message = p.nextTextValue() + } + } return getError(code, message) } }