Не подтверждена Коммит c50d8075 создал по автору Rustam's avatar Rustam Зафиксировано автором GitHub
Просмотр файлов

KTOR-2007 Fix running custom response validation without default one (#2340)

владелец 9855875d
......@@ -45,7 +45,7 @@ public class HttpClientConfig<T : HttpClientEngineConfig> {
public var useDefaultTransformers: Boolean by shared(true)
/**
* Terminate [HttpClient.receivePipeline] if status code is not success(>=300).
* Terminate [HttpClient.receivePipeline] if status code is not successful (>=300).
*/
public var expectSuccess: Boolean by shared(true)
......
......@@ -21,12 +21,20 @@ private val ValidateMark = AttributeKey<Unit>("ValidateMark")
*/
public fun HttpClientConfig<*>.addDefaultResponseValidation() {
HttpResponseValidator {
@Suppress("DEPRECATION")
expectSuccess = this@addDefaultResponseValidation.expectSuccess
validateResponse { response ->
val expectSuccess = response.call.attributes[ExpectSuccessAttributeKey]
if (!expectSuccess) {
return@validateResponse
}
val statusCode = response.status.value
val originCall = response.call
if (statusCode < 300 || originCall.attributes.contains(ValidateMark)) return@validateResponse
if (statusCode < 300 || originCall.attributes.contains(ValidateMark)) {
return@validateResponse
}
val exceptionCall = originCall.save().apply {
attributes.put(ValidateMark, Unit)
......
......@@ -5,12 +5,13 @@
package io.ktor.client.features
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.features.HttpCallValidator.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.utils.*
import io.ktor.util.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import kotlin.native.concurrent.*
/**
......@@ -66,8 +67,13 @@ public class HttpCallValidator internal constructor(
internal val responseExceptionHandlers: MutableList<CallExceptionHandler> = mutableListOf()
/**
* Terminate [HttpClient.receivePipeline] if status code is not success(>=300).
* Terminate [HttpClient.receivePipeline] if status code is not successful (>=300).
*/
@Deprecated(
"This property is ignored. Please use `expectSuccess` property in HttpClientConfig. " +
"This is going to become internal."
)
public var expectSuccess: Boolean = true
/**
......@@ -103,6 +109,7 @@ public class HttpCallValidator internal constructor(
override fun install(feature: HttpCallValidator, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.Before) {
try {
context.attributes.computeIfAbsent(ExpectSuccessAttributeKey) { feature.expectSuccess }
proceedWith(it)
} catch (cause: Throwable) {
val unwrappedCause = cause.unwrapCancellationException()
......@@ -124,10 +131,7 @@ public class HttpCallValidator internal constructor(
}
scope[HttpSend].intercept { call, _ ->
val expectSuccess = call.attributes.getOrNull(ExpectSuccessAttributeKey) ?: feature.expectSuccess
if (expectSuccess) {
feature.validateResponse(call.response)
}
feature.validateResponse(call.response)
call
}
}
......@@ -141,9 +145,12 @@ public fun HttpClientConfig<*>.HttpResponseValidator(block: HttpCallValidator.Co
install(HttpCallValidator, block)
}
/**
* Terminate [HttpClient.receivePipeline] if status code is not successful (>=300).
*/
public var HttpRequestBuilder.expectSuccess: Boolean
get() = attributes.getOrNull(ExpectSuccessAttributeKey) ?: true
set(value) = attributes.put(ExpectSuccessAttributeKey, value)
@SharedImmutable
private val ExpectSuccessAttributeKey = AttributeKey<Boolean>("ExpectSuccessAttribyteKey")
internal val ExpectSuccessAttributeKey = AttributeKey<Boolean>("ExpectSuccessAttribyteKey")
......@@ -316,6 +316,88 @@ class CallValidatorTest {
}
}
}
@Test
fun testCustomResponseValidationRunsAfterDefault() = testWithEngine(MockEngine) {
config {
expectSuccess = true
engine {
addHandler {
val status = HttpStatusCode(900, "Awesome code")
respond("Awesome response", status)
}
}
HttpResponseValidator {
validateResponse {
throw IllegalStateException("Should not throw")
}
}
}
test { client ->
try {
client.get<String>()
fail("Should fail")
} catch (cause: ResponseException) {
assertEquals(900, cause.response.status.value)
assertEquals("Awesome response", cause.response.receive())
}
}
}
@Test
fun testCustomResponseValidationWithoutDefault() = testWithEngine(MockEngine) {
config {
expectSuccess = false
engine {
addHandler {
val status = HttpStatusCode(900, "Awesome code")
respond("Awesome response", status)
}
}
HttpResponseValidator {
validateResponse {
throw IllegalStateException("My custom error")
}
}
}
test { client ->
try {
client.get<String>()
fail("Should fail")
} catch (cause: IllegalStateException) {
assertEquals("My custom error", cause.message)
}
}
}
@Test
fun testCustomResponseValidationWithoutDefaultPerRequestLevel() = testWithEngine(MockEngine) {
config {
expectSuccess = true
engine {
addHandler {
val status = HttpStatusCode(900, "Awesome code")
respond("Awesome response", status)
}
}
HttpResponseValidator {
validateResponse {
throw IllegalStateException("My custom error")
}
}
}
test { client ->
try {
client.get<String> { expectSuccess = false }
fail("Should fail")
} catch (cause: IllegalStateException) {
assertEquals("My custom error", cause.message)
}
}
}
}
internal class CallValidatorTestException : Throwable()
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать