commonMain.aws.smithy.kotlin.runtime.httptest.HttpTrafficParser.kt Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.httptest
import aws.smithy.kotlin.runtime.http.*
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder
import aws.smithy.kotlin.runtime.http.request.url
import aws.smithy.kotlin.runtime.http.response.HttpResponse
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.text.encoding.decodeBase64Bytes
import kotlinx.serialization.json.*
internal fun parseHttpTraffic(json: String) = buildTestConnection {
require(json.isNotEmpty()) { "invalid HTTP traffic JSON document" }
val traffic = Json.parseToJsonElement(json).jsonArray
for (call in traffic.map { it.jsonObject }) {
val reqObj = call["request"] ?: error("expected `request` in document")
val respObj = checkNotNull(call["response"]) { "expected `response` key in document " }.jsonObject
val req = parseRequest(reqObj)
val resp = parseResponse(respObj)
if (req != null) {
expect(req, resp)
} else {
// no assertions made for null requests
expect(resp)
}
}
}
private enum class BodyContentType {
UTF_8,
BINARY,
;
companion object {
fun fromValue(value: String): BodyContentType = when (value.lowercase()) {
"utf8" -> UTF_8
"binary" -> BINARY
else -> error("invalid body content type: $value")
}
}
}
private fun parseRequest(req: JsonElement): HttpRequest? = when (req) {
// no assertions will be made
is JsonNull -> null
is JsonObject -> {
val method = req["method"]?.jsonPrimitive?.content ?: "GET"
val uri = req["uri"]?.jsonPrimitive?.content ?: error("expected `uri` in request")
val headersObj = req["headers"]?.jsonObject
// by default assumed to be UTF-8 string.
val bodyContentType = req["bodyContentType"]?.let { BodyContentType.fromValue(it.jsonPrimitive.content) } ?: BodyContentType.UTF_8
val body = req["body"]?.jsonPrimitive?.content
val builder = HttpRequestBuilder()
builder.method = HttpMethod.parse(method)
builder.url(Url.parse(uri))
val headers = headersObj?.let(::convertHeaders) ?: Headers.Empty
builder.headers.appendAll(headers)
if (body != null) {
builder.body = convertBody(body, bodyContentType)
}
builder.build()
}
else -> error("expected object or `null` for `request` key")
}
private fun parseResponse(resp: JsonObject): HttpResponse {
val status = resp["status"]?.jsonPrimitive?.intOrNull ?: error("expected http status in response object")
val headersObj = resp["headers"]?.jsonObject
val bodyContentType = resp["bodyContentType"]?.let { BodyContentType.fromValue(it.jsonPrimitive.content) } ?: BodyContentType.UTF_8
val body = resp["body"]?.jsonPrimitive?.content
val headers = headersObj?.let(::convertHeaders) ?: Headers.Empty
val httpBody = if (body != null) {
convertBody(body, bodyContentType)
} else {
HttpBody.Empty
}
return HttpResponse(HttpStatusCode.fromValue(status), headers, httpBody)
}
private fun convertHeaders(headers: JsonObject): Headers {
val builder = HeadersBuilder()
headers.forEach { hdr ->
when (val value = hdr.value) {
is JsonPrimitive -> builder.append(hdr.key, value.content)
is JsonArray -> builder.appendAll(hdr.key, value.map { it.jsonPrimitive.content })
else -> error("invalid header value for key: `${hdr.key}`")
}
}
return builder.build()
}
private fun convertBody(body: String, bodyContentType: BodyContentType): HttpBody {
val payload = when (bodyContentType) {
BodyContentType.UTF_8 -> body.encodeToByteArray()
BodyContentType.BINARY -> body.decodeBase64Bytes()
}
return HttpBody.fromBytes(payload)
}