From 2d3b6d1e5273186b10d2d88532e70e269de090c7 Mon Sep 17 00:00:00 2001 From: Nasser Anssari Date: Sun, 2 Jun 2024 14:03:30 +0300 Subject: [PATCH] fix: add a deprecated getBody to Responses, and fix operation execution with empty responses (#564) fix: add a deprecated getBody to Response fix: execution of operations with empty responses chore: add tests for TransactionId chore: clean up apiExceptions mustache helper chore: clean up mustache helpers --- .../expediagroup/sdk/core/model/Response.kt | 5 +- .../sdk/core/model/TransactionIdTest.kt | 30 +++++++++ .../sdk/generators/openapi/MustacheHelpers.kt | 29 ++++----- .../expediagroup-sdk/client.mustache | 63 ++++++++++++------- .../models/apiException.mustache | 4 +- 5 files changed, 89 insertions(+), 42 deletions(-) create mode 100644 core/src/test/kotlin/com/expediagroup/sdk/core/model/TransactionIdTest.kt diff --git a/core/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt b/core/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt index dec767c8b..d5bd9747e 100644 --- a/core/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt +++ b/core/src/main/kotlin/com/expediagroup/sdk/core/model/Response.kt @@ -46,7 +46,10 @@ open class Response( ) } - override fun toString(): String = "Response(statusCode=$statusCode, data=$data, headers=$headers)" + override fun toString() = "Response(statusCode=$statusCode, data=$data, headers=$headers)" + + @Deprecated("Use getData() instead", replaceWith = ReplaceWith("getData()")) + fun getBody() = data } class EmptyResponse( diff --git a/core/src/test/kotlin/com/expediagroup/sdk/core/model/TransactionIdTest.kt b/core/src/test/kotlin/com/expediagroup/sdk/core/model/TransactionIdTest.kt new file mode 100644 index 000000000..0949ae3ae --- /dev/null +++ b/core/src/test/kotlin/com/expediagroup/sdk/core/model/TransactionIdTest.kt @@ -0,0 +1,30 @@ +package com.expediagroup.sdk.core.model + +import io.mockk.every +import io.mockk.mockkStatic +import org.junit.jupiter.api.Test +import java.util.UUID + +class TransactionIdTest { + private val uuids = listOf(UUID.randomUUID(), UUID.randomUUID()) + + @Test + fun `peek should return a UUID`() { + mockkStatic(UUID::class) { + every { UUID.randomUUID() } returnsMany uuids + val transactionId = TransactionId() + assert(transactionId.peek() == uuids[0]) + assert(transactionId.peek() == uuids[0]) + } + } + + @Test + fun `dequeue should return a UUID and change it`() { + mockkStatic(UUID::class) { + every { UUID.randomUUID() } returnsMany uuids + val transactionId = TransactionId() + assert(transactionId.dequeue() == uuids[0]) + assert(transactionId.peek() == uuids[1]) + } + } +} diff --git a/generator/openapi/src/main/kotlin/com/expediagroup/sdk/generators/openapi/MustacheHelpers.kt b/generator/openapi/src/main/kotlin/com/expediagroup/sdk/generators/openapi/MustacheHelpers.kt index d6528f189..009a08f2b 100644 --- a/generator/openapi/src/main/kotlin/com/expediagroup/sdk/generators/openapi/MustacheHelpers.kt +++ b/generator/openapi/src/main/kotlin/com/expediagroup/sdk/generators/openapi/MustacheHelpers.kt @@ -21,26 +21,23 @@ import org.openapitools.codegen.CodegenOperation import org.openapitools.codegen.CodegenProperty import org.openapitools.codegen.model.ApiInfoMap -val fallbackBody = fun(dataType: String): String { - return if (dataType.startsWith("kotlin.collections.List")) { - "emptyList()" - } else if (dataType.startsWith("kotlin.collections.Map")) { - "emptyMap()" - } else if (dataType.startsWith("kotlin.collections.Set")) { - "emptySet()" - } else { - "" - } -} - val mustacheHelpers = mapOf( "isPaginatable" to { Mustache.Lambda { fragment, writer -> val operation = fragment.context() as CodegenOperation + if (operation.returnType == null) return@Lambda val paginationHeaders = listOf("Pagination-Total-Results", "Link") val availableHeaders = operation.responses.find { it.code == "200" }?.headers?.filter { it.baseName in paginationHeaders } if (availableHeaders?.size == paginationHeaders.size) { - val context = mapOf("fallbackBody" to fallbackBody(operation.returnType)) + val fallbackBody = + when { + operation.returnType.startsWith("kotlin.collections.List") -> "emptyList()" + operation.returnType.startsWith("kotlin.collections.Map") -> "emptyMap()" + operation.returnType.startsWith("kotlin.collections.Set") -> "emptySet()" + else -> "" + } + + val context = mapOf("fallbackBody" to fallbackBody) fragment.execute(context, writer) } } @@ -83,10 +80,8 @@ val mustacheHelpers = mapOf( } dataTypes.forEach { dataType -> - writer.write( - "class ExpediaGroupApi${dataType}Exception(code: Int, override val errorObject: $dataType, transactionId: String?) : " + - "ExpediaGroupApiException(code, errorObject, transactionId)\n" - ) + val context = mapOf("dataType" to dataType) + fragment.execute(context, writer) } } }, diff --git a/generator/openapi/src/main/resources/templates/expediagroup-sdk/client.mustache b/generator/openapi/src/main/resources/templates/expediagroup-sdk/client.mustache index b7e5d979d..993529354 100644 --- a/generator/openapi/src/main/resources/templates/expediagroup-sdk/client.mustache +++ b/generator/openapi/src/main/resources/templates/expediagroup-sdk/client.mustache @@ -38,27 +38,42 @@ class {{#lambda.titlecase}}{{namespace}}{{/lambda.titlecase}}Client private cons throw ErrorObjectMapper.process(response, operationId) } - private inline fun execute(operation: Operation) : Response { + private suspend inline fun executeHttpRequest(operation: Operation): HttpResponse { + return httpClient.request { + method = HttpMethod.parse(operation.method) + url(operation.url) + + operation.params.getHeaders()?.forEach { (key, value) -> + headers.append(key, value) + } + + operation.params.getQueryParams()?.forEach { (key, value) -> + url.parameters.appendAll(key, value) + } + + appendHeaders(operation.transactionId) + validateConstraints(operation.requestBody) + contentType(ContentType.Application.Json) + setBody(operation.requestBody) + } + } + + private inline fun executeWithEmptyResponse(operation: Operation) : EmptyResponse { try { return GlobalScope.future(Dispatchers.IO) { - val response = httpClient.request { - method = HttpMethod.parse(operation.method) - url(operation.url) - - operation.params.getHeaders()?.forEach { (key, value) -> - headers.append(key, value) - } - - operation.params.getQueryParams()?.forEach { (key, value) -> - url.parameters.appendAll(key, value) - } - - appendHeaders(operation.transactionId) - validateConstraints(operation.requestBody) - contentType(ContentType.Application.Json) - setBody(operation.requestBody) - } + val response = executeHttpRequest(operation) + throwIfError(response, operation.operationId) + EmptyResponse(response.status.value, response.headers.entries()) + }.get() + } catch (exception: Exception) { + exception.handle() + } + } + private inline fun execute(operation: Operation) : Response { + try { + return GlobalScope.future(Dispatchers.IO) { + val response = executeHttpRequest(operation) throwIfError(response, operation.operationId) Response(response.status.value, response.body(), response.headers.entries()) }.get() @@ -75,11 +90,13 @@ class {{#lambda.titlecase}}{{namespace}}{{/lambda.titlecase}}Client private cons {{#throwsExceptions}}{{/throwsExceptions}} * @return a [Response] object with a body of type {{{returnType}}}{{^returnType}}Nothing{{/returnType}} */ - fun execute(operation: {{operationIdCamelCase}}Operation) : {{#returnType}}Response<{{{returnType}}}>{{/returnType}}{{^returnType}}Response{{/returnType}} { - return execute< - {{#bodyParam}}{{dataType}}{{/bodyParam}}{{^hasBodyParam}}Nothing{{/hasBodyParam}}, - {{{returnType}}}{{^returnType}}Nothing{{/returnType}} - >(operation) + fun execute(operation: {{operationIdCamelCase}}Operation) : {{#returnType}}Response<{{{returnType}}}>{{/returnType}}{{^returnType}}EmptyResponse{{/returnType}} { + {{#returnType}} + return execute<{{#bodyParam}}{{dataType}}{{/bodyParam}}{{^hasBodyParam}}Nothing{{/hasBodyParam}}, {{{returnType}}}>(operation) + {{/returnType}} + {{^returnType}} + return executeWithEmptyResponse<{{#bodyParam}}{{dataType}}{{/bodyParam}}{{^hasBodyParam}}Nothing{{/hasBodyParam}}>(operation) + {{/returnType}} } private suspend {{^isKotlin}}inline {{/isKotlin}}fun {{^isKotlin}}k{{/isKotlin}}{{operationId}}WithResponse({{>client/apiParamsDecleration}}): {{#returnType}}Response<{{{returnType}}}>{{/returnType}}{{^returnType}}Response{{/returnType}} { diff --git a/generator/openapi/src/main/resources/templates/expediagroup-sdk/models/apiException.mustache b/generator/openapi/src/main/resources/templates/expediagroup-sdk/models/apiException.mustache index 54a752b4e..8002f4abf 100644 --- a/generator/openapi/src/main/resources/templates/expediagroup-sdk/models/apiException.mustache +++ b/generator/openapi/src/main/resources/templates/expediagroup-sdk/models/apiException.mustache @@ -60,5 +60,7 @@ internal object ErrorObjectMapper { } {{#apiInfo}} - {{#defineApiExceptions}}{{/defineApiExceptions}} + {{#defineApiExceptions}} + class ExpediaGroupApi{{dataType}}Exception(code: Int, override val errorObject: {{dataType}}, transactionId: String?) : ExpediaGroupApiException(code, errorObject, transactionId) + {{/defineApiExceptions}} {{/apiInfo}} \ No newline at end of file