From b87214452b0692cb8e11d72787675d9f95439fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=80=C9=B4=E1=B4=9B=E1=B4=8F=C9=B4?= Date: Tue, 20 Dec 2022 18:07:44 +0300 Subject: [PATCH] Added support for optional `List` and `UUID` parameters in http server controllers (#24) --- .../annotation/processor/ReturnType.java | 1 - .../client/ClientWithQueryParams.java | 2 - .../processor/client/GithubClient.java | 6 +- .../request/HttpClientRequestBuilder.java | 6 +- .../http/client/jdk/JdkHttpClientHeaders.java | 4 +- .../extension/HttpClientKoraExtension.kt | 5 +- .../HttpClientSymbolProcessorTest.kt | 2 +- .../symbol/processor/client/GithubClient.kt | 6 +- .../kora/http/common/HttpHeadersImplTest.java | 2 + .../processor/RequestHandlerGenerator.java | 144 ++++-- .../controller/MultipleParamsController.java | 3 - .../TestControllerWithCustomReaders.java | 7 +- .../TestControllerWithResponseEntity.java | 2 - .../http/server/common/PrivateApiHandler.java | 4 - .../common/handler/RequestHandlerUtils.java | 425 +++++++++++++----- .../common/handler/ParameterExtractors.kt | 372 +++++++++------ .../http/server/common/HttpServerTestKit.java | 2 +- .../symbol/procesor/ExtractorFunction.kt | 32 +- .../procesor/HttpControllerProcessor.kt | 15 +- .../server/symbol/procesor/RouteProcessor.kt | 99 +++- .../controllers/MultipleParamsController.kt | 4 +- .../TestControllerHeaderParameters.kt | 1 - .../TestControllerKotlinSuspendHandler.kt | 2 - .../TestControllerWithCustomReaders.kt | 10 +- .../controllers/TestControllerWithMappers.kt | 2 +- .../TestControllerWithResponseEntity.kt | 2 +- .../processor/server/HttpResponseAssert.kt | 2 +- .../server/undertow/UndertowHttpServer.java | 4 - .../http/server/undertow/UndertowModule.java | 2 - .../undertow/UndertowPrivateApiHandler.java | 3 - .../undertow/UndertowPrivateHttpServer.java | 3 - .../undertow/UndertowPublicApiHandler.java | 1 - 32 files changed, 783 insertions(+), 392 deletions(-) diff --git a/http/http-client-annotation-processor/src/main/java/ru/tinkoff/kora/http/client/annotation/processor/ReturnType.java b/http/http-client-annotation-processor/src/main/java/ru/tinkoff/kora/http/client/annotation/processor/ReturnType.java index d4e43fb62..ad49831d9 100644 --- a/http/http-client-annotation-processor/src/main/java/ru/tinkoff/kora/http/client/annotation/processor/ReturnType.java +++ b/http/http-client-annotation-processor/src/main/java/ru/tinkoff/kora/http/client/annotation/processor/ReturnType.java @@ -10,7 +10,6 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; diff --git a/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/ClientWithQueryParams.java b/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/ClientWithQueryParams.java index 77efda10a..2a0d37077 100644 --- a/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/ClientWithQueryParams.java +++ b/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/ClientWithQueryParams.java @@ -3,12 +3,10 @@ import ru.tinkoff.kora.http.client.common.annotation.HttpClient; import ru.tinkoff.kora.http.common.HttpMethod; -import ru.tinkoff.kora.http.common.annotation.Header; import ru.tinkoff.kora.http.common.annotation.HttpRoute; import ru.tinkoff.kora.http.common.annotation.Query; import javax.annotation.Nullable; -import java.time.LocalDate; import java.util.List; @HttpClient(configPath = "clientWithQueryParams") diff --git a/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/GithubClient.java b/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/GithubClient.java index e8b2858cc..80f9ba9e0 100644 --- a/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/GithubClient.java +++ b/http/http-client-annotation-processor/src/test/java/ru/tinkoff/kora/http/client/annotation/processor/client/GithubClient.java @@ -9,10 +9,10 @@ import ru.tinkoff.kora.http.client.common.request.HttpClientRequest; import ru.tinkoff.kora.http.client.common.response.HttpClientResponse; import ru.tinkoff.kora.http.common.HttpMethod; -import ru.tinkoff.kora.http.common.annotation.*; +import ru.tinkoff.kora.http.common.annotation.HttpRoute; +import ru.tinkoff.kora.http.common.annotation.InterceptWith; +import ru.tinkoff.kora.http.common.annotation.Path; -import javax.annotation.Nullable; -import java.time.LocalDate; import java.util.List; import java.util.function.Function; diff --git a/http/http-client-common/src/main/java/ru/tinkoff/kora/http/client/common/request/HttpClientRequestBuilder.java b/http/http-client-common/src/main/java/ru/tinkoff/kora/http/client/common/request/HttpClientRequestBuilder.java index a43fc8719..ac1163ce1 100644 --- a/http/http-client-common/src/main/java/ru/tinkoff/kora/http/client/common/request/HttpClientRequestBuilder.java +++ b/http/http-client-common/src/main/java/ru/tinkoff/kora/http/client/common/request/HttpClientRequestBuilder.java @@ -6,11 +6,7 @@ import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/http/http-client-jdk/src/main/java/ru/tinkoff/kora/http/client/jdk/JdkHttpClientHeaders.java b/http/http-client-jdk/src/main/java/ru/tinkoff/kora/http/client/jdk/JdkHttpClientHeaders.java index 0caf10e05..8cf01ab00 100644 --- a/http/http-client-jdk/src/main/java/ru/tinkoff/kora/http/client/jdk/JdkHttpClientHeaders.java +++ b/http/http-client-jdk/src/main/java/ru/tinkoff/kora/http/client/jdk/JdkHttpClientHeaders.java @@ -1,5 +1,5 @@ package ru.tinkoff.kora.http.client.jdk; -; + import ru.tinkoff.kora.http.common.HttpHeaders; import javax.annotation.Nonnull; @@ -8,6 +8,8 @@ import java.util.List; import java.util.Map; +; + public class JdkHttpClientHeaders implements HttpHeaders { private final Map> headers; diff --git a/http/http-client-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/client/symbol/processor/extension/HttpClientKoraExtension.kt b/http/http-client-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/client/symbol/processor/extension/HttpClientKoraExtension.kt index 5fde9a934..ca33741fd 100644 --- a/http/http-client-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/client/symbol/processor/extension/HttpClientKoraExtension.kt +++ b/http/http-client-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/client/symbol/processor/extension/HttpClientKoraExtension.kt @@ -1,6 +1,9 @@ package ru.tinkoff.kora.http.client.symbol.processor.extension -import com.google.devtools.ksp.* +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.getClassDeclarationByName +import com.google.devtools.ksp.getConstructors import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.KSClassDeclaration diff --git a/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/HttpClientSymbolProcessorTest.kt b/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/HttpClientSymbolProcessorTest.kt index 0dedf7172..7b43f36d7 100644 --- a/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/HttpClientSymbolProcessorTest.kt +++ b/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/HttpClientSymbolProcessorTest.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.registerKotlinModule import com.google.devtools.ksp.KspExperimental -import org.mockito.kotlin.any import io.opentelemetry.api.trace.TracerProvider import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions @@ -15,6 +14,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.mockito.kotlin.any import org.mockserver.integration.ClientAndServer import org.mockserver.model.HttpRequest import org.mockserver.model.HttpResponse diff --git a/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/client/GithubClient.kt b/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/client/GithubClient.kt index 2014630a5..a0dd7e55e 100644 --- a/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/client/GithubClient.kt +++ b/http/http-client-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/client/symbol/processor/client/GithubClient.kt @@ -8,11 +8,7 @@ import ru.tinkoff.kora.http.client.common.interceptor.HttpClientInterceptor import ru.tinkoff.kora.http.client.common.request.HttpClientRequest import ru.tinkoff.kora.http.client.common.response.HttpClientResponse import ru.tinkoff.kora.http.common.HttpMethod -import ru.tinkoff.kora.http.common.annotation.Header -import ru.tinkoff.kora.http.common.annotation.HttpRoute -import ru.tinkoff.kora.http.common.annotation.InterceptWith -import ru.tinkoff.kora.http.common.annotation.Path -import ru.tinkoff.kora.http.common.annotation.Query +import ru.tinkoff.kora.http.common.annotation.* import java.util.function.Function @HttpClient(telemetryTag = [GithubClient::class], httpClientTag = [GithubClient::class]) diff --git a/http/http-common/src/test/java/ru/tinkoff/kora/http/common/HttpHeadersImplTest.java b/http/http-common/src/test/java/ru/tinkoff/kora/http/common/HttpHeadersImplTest.java index fb2fc29be..b25665049 100644 --- a/http/http-common/src/test/java/ru/tinkoff/kora/http/common/HttpHeadersImplTest.java +++ b/http/http-common/src/test/java/ru/tinkoff/kora/http/common/HttpHeadersImplTest.java @@ -9,6 +9,8 @@ import java.util.Map; import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertEquals; + class HttpHeadersImplTest { @Test diff --git a/http/http-server-annotation-processor/src/main/java/ru/tinkoff/kora/http/server/annotation/processor/RequestHandlerGenerator.java b/http/http-server-annotation-processor/src/main/java/ru/tinkoff/kora/http/server/annotation/processor/RequestHandlerGenerator.java index 5c2035415..6471e625b 100644 --- a/http/http-server-annotation-processor/src/main/java/ru/tinkoff/kora/http/server/annotation/processor/RequestHandlerGenerator.java +++ b/http/http-server-annotation-processor/src/main/java/ru/tinkoff/kora/http/server/annotation/processor/RequestHandlerGenerator.java @@ -247,7 +247,13 @@ private CodeBlock defineHeaderParameter(Parameter parameter, MethodSpec.Builder } case "java.util.Optional" -> code.add("var $L = $T.ofNullable($T.parseOptionalStringHeaderParameter(_request, $S));", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name); - case "java.util.List" -> code.add("var $L = $T.parseStringListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalStringListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseStringListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } case "java.util.Optional" -> code.add("var $L = $T.ofNullable($T.parseOptionalIntegerHeaderParameter(_request, $S));", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name); @@ -258,7 +264,13 @@ private CodeBlock defineHeaderParameter(Parameter parameter, MethodSpec.Builder code.add("var $L = $T.parseIntegerHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseIntegerListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalIntegerListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseIntegerListHeaderParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } default -> { var listElement = elements.getTypeElement(List.class.getCanonicalName()); @@ -268,34 +280,36 @@ private CodeBlock defineHeaderParameter(Parameter parameter, MethodSpec.Builder if (this.types.isAssignable(parameter.type, optionalErasure)) { var optionalParameter = ((DeclaredType) parameter.type).getTypeArguments().get(0); - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - optionalParameter - ); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, optionalParameter); var parameterReaderName = "_" + parameter.variableElement.getSimpleName().toString() + "Reader"; + methodBuilder.addParameter(TypeName.get(parameterReaderType), parameterReaderName); code.add("var $L = $T.ofNullable($T.parseOptionalStringHeaderParameter(_request, $S)).map($L::read);", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name, parameterReaderName); return code.build(); } + if (this.types.isAssignable(parameter.type, listErasure)) { var listParameter = ((DeclaredType) parameter.type).getTypeArguments().get(0); - - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - listParameter - ); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, listParameter); methodBuilder.addParameter(TypeName.get(parameterReaderType), "_" + parameter.name + "Reader"); - code.add("var $L = $T.parseStringListHeaderParameter(_request, $S).stream().map($L::read).toList();", parameter.variableElement, RequestHandlerUtils.class, parameter.name, "_" + parameter.name + "Reader"); - return code.build(); + if (isNullable(parameter)) { + code.add(""" + var _optional_$L = $T.parseOptionalStringListHeaderParameter(_request, $S); + var $L = (_optional_$L == null) ? null : _optional_$L.stream().map($L::read).toList(); + """, parameter.variableElement, RequestHandlerUtils.class, parameter.name, + parameter.variableElement, parameter.variableElement, parameter.variableElement, "_" + parameter.name + "Reader"); + } else { + code.add("var $L = $T.parseStringListHeaderParameter(_request, $S).stream().map($L::read).toList();", parameter.variableElement, RequestHandlerUtils.class, parameter.name, "_" + parameter.name + "Reader"); + } + + return code.build(); } - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - parameter.type - ); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, parameter.type); var parameterReaderName = "_" + parameter.variableElement.getSimpleName() + "Reader"; methodBuilder.addParameter(TypeName.get(parameterReaderType), parameterReaderName); + if (isNullable(parameter)) { var transitParameterName = "_" + parameter.variableElement.getSimpleName() + "RawValue"; code.add("var $L = $T.parseOptionalStringHeaderParameter(_request, $S);\n", transitParameterName, RequestHandlerUtils.class, parameter.name); @@ -305,7 +319,6 @@ private CodeBlock defineHeaderParameter(Parameter parameter, MethodSpec.Builder } return code.build(); } - } return code.build(); } @@ -314,6 +327,23 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m var code = CodeBlock.builder(); var typeString = parameter.type.toString(); switch (typeString) { + case "java.util.UUID" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalUuidQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseUuidQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } + case "java.util.Optional" -> + code.add("var $L = $T.ofNullable($T.parseOptionalUuidQueryParameter(_request, $S));", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalUuidListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseUuidListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } + case "int" -> code.add("var $L = $T.parseIntegerQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); case "java.util.Optional" -> code.add("var $L = $T.ofNullable($T.parseOptionalIntegerQueryParameter(_request, $S));", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name); @@ -324,7 +354,13 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m code.add("var $L = $T.parseIntegerQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseIntegerListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalIntegerListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseIntegerListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } case "long" -> code.add("var $L = $T.parseLongQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); case "java.util.Optional" -> @@ -336,7 +372,13 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m code.add("var $L = $T.parseLongQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseLongListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalLongListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseLongListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } case "double" -> code.add("var $L = $T.parseDoubleQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); case "java.util.Optional" -> @@ -348,7 +390,13 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m code.add("var $L = $T.parseDoubleQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseDoubleListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalDoubleListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseDoubleListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } case "java.util.Optional" -> code.add("var $L = $T.ofNullable($T.parseOptionalStringQueryParameter(_request, $S));", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name); @@ -359,8 +407,13 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m code.add("var $L = $T.parseStringQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseStringListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); - + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalStringListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseStringListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } case "boolean" -> code.add("var $L = $T.parseBooleanQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); case "java.util.Optional" -> @@ -372,48 +425,51 @@ private CodeBlock defineQueryParameter(Parameter parameter, MethodSpec.Builder m code.add("var $L = $T.parseBooleanQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); } } - case "java.util.List" -> code.add("var $L = $T.parseBooleanListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); - + case "java.util.List" -> { + if (isNullable(parameter)) { + code.add("var $L = $T.parseOptionalBooleanListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } else { + code.add("var $L = $T.parseBooleanListQueryParameter(_request, $S);", parameter.variableElement, RequestHandlerUtils.class, parameter.name); + } + } default -> { var listElement = elements.getTypeElement(List.class.getCanonicalName()); var listErasure = types.erasure(listElement.asType()); var optionalElement = elements.getTypeElement(Optional.class.getCanonicalName()); var optionalErasure = types.erasure(optionalElement.asType()); - - String readerParameterName = "_" + parameter.name + "Reader"; + final String readerParameterName = "_" + parameter.name + "Reader"; if (this.types.isAssignable(parameter.type, optionalErasure)) { var optionalParameter = ((DeclaredType) parameter.type).getTypeArguments().get(0); - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - optionalParameter - ); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, optionalParameter); + methodBuilder.addParameter(TypeName.get(parameterReaderType), readerParameterName); code.add("var $L = $T.ofNullable($T.parseOptionalStringQueryParameter(_request, $S)).map($L::read);", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name, readerParameterName); return code.build(); } if (this.types.isAssignable(parameter.type, listErasure)) { var listParameter = ((DeclaredType) parameter.type).getTypeArguments().get(0); - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - listParameter - ); - methodBuilder.addParameter(TypeName.get(parameterReaderType), readerParameterName); - code.add("var $L = $T.parseStringListQueryParameter(_request, $S).stream().map($L::read).toList();", parameter.variableElement, RequestHandlerUtils.class, parameter.name, readerParameterName); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, listParameter); + + if (isNullable(parameter)) { + methodBuilder.addParameter(TypeName.get(parameterReaderType), readerParameterName); + code.add("var $L = $T.parseStringListQueryParameter(_request, $S).stream().map($L::read).toList();", parameter.variableElement, RequestHandlerUtils.class, parameter.name, readerParameterName); + } else { + methodBuilder.addParameter(TypeName.get(parameterReaderType), readerParameterName); + code.add("var $L = $T.ofNullable($T.parseOptionalStringListQueryParameter(_request, $S)).map(_var_$L -> _var_$L.stream().map($L::read).toList()).orElse(null);", + parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name, parameter.variableElement, parameter.variableElement, readerParameterName); + } + return code.build(); } - var parameterReaderType = this.types.getDeclaredType( - this.stringParameterReaderElement, - parameter.type - ); + var parameterReaderType = this.types.getDeclaredType(this.stringParameterReaderElement, parameter.type); methodBuilder.addParameter(TypeName.get(parameterReaderType), readerParameterName); + if (isNullable(parameter)) { - var transitParameterName = "_" + parameter.variableElement.getSimpleName() + "RawValue"; - code.add("var $L = $T.parseOptionalStringQueryParameter(_request, $S);\n", transitParameterName, RequestHandlerUtils.class, parameter.name); - code.add("var $L = $L == null ? null : $L.read($L);", parameter.variableElement, transitParameterName, readerParameterName, transitParameterName); + code.add("var $L = $T.ofNullable($T.parseOptionalStringQueryParameter(_request, $S)).map($L::read).orElse(null);", parameter.variableElement, Optional.class, RequestHandlerUtils.class, parameter.name, readerParameterName); } else { code.add("var $L = $L.read($T.parseStringQueryParameter(_request, $S));", parameter.variableElement, readerParameterName, RequestHandlerUtils.class, parameter.name); } diff --git a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/MultipleParamsController.java b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/MultipleParamsController.java index b4ea1bbc5..2bcc3d7e9 100644 --- a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/MultipleParamsController.java +++ b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/MultipleParamsController.java @@ -2,7 +2,6 @@ import reactor.core.publisher.Mono; import ru.tinkoff.kora.common.Mapping; -import ru.tinkoff.kora.http.common.HttpHeaders; import ru.tinkoff.kora.http.common.HttpMethod; import ru.tinkoff.kora.http.common.annotation.HttpRoute; import ru.tinkoff.kora.http.server.common.HttpServerRequest; @@ -10,8 +9,6 @@ import ru.tinkoff.kora.http.server.common.annotation.HttpController; import ru.tinkoff.kora.http.server.common.handler.HttpServerRequestMapper; -import java.nio.charset.StandardCharsets; - @HttpController public class MultipleParamsController { public record Param1() {} diff --git a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithCustomReaders.java b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithCustomReaders.java index d2b0bd988..db1c85367 100644 --- a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithCustomReaders.java +++ b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithCustomReaders.java @@ -19,10 +19,11 @@ public class TestControllerWithCustomReaders { @HttpRoute(method = GET, path = "/test/{pathEntity}") - public HttpServerResponseEntity test(@Query("queryEntity") List queryList, @Path("pathEntity") ReadableEntity pathEntity, @Header("header-Entity") Optional headerEntity) { - var resultList = new ArrayList<>(queryList); + public HttpServerResponseEntity test(@Path("pathEntity") ReadableEntity pathEntity, + @Nullable @Query("queryEntity") List queryList, + @Header("header-Entity") Optional headerEntity) { + var resultList = queryList == null ? new ArrayList() : new ArrayList<>(queryList); resultList.add(pathEntity); - return new HttpServerResponseEntity<>(200, resultList.stream().map(ReadableEntity::string).collect(Collectors.joining(", "))); } } diff --git a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithResponseEntity.java b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithResponseEntity.java index e2ce969ae..7ceeecde1 100644 --- a/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithResponseEntity.java +++ b/http/http-server-annotation-processor/src/test/java/ru/tinkoff/kora/http/server/annotation/processor/controller/TestControllerWithResponseEntity.java @@ -6,8 +6,6 @@ import ru.tinkoff.kora.http.server.common.HttpServerResponseEntity; import ru.tinkoff.kora.http.server.common.annotation.HttpController; -import java.nio.ByteBuffer; - import static ru.tinkoff.kora.http.common.HttpMethod.GET; @HttpController diff --git a/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/PrivateApiHandler.java b/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/PrivateApiHandler.java index f2b4d2882..9ed6215c0 100644 --- a/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/PrivateApiHandler.java +++ b/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/PrivateApiHandler.java @@ -11,12 +11,8 @@ import ru.tinkoff.kora.common.readiness.ReadinessProbe; import ru.tinkoff.kora.common.readiness.ReadinessProbeFailure; import ru.tinkoff.kora.http.common.HttpHeaders; -import ru.tinkoff.kora.http.common.HttpResultCode; -import ru.tinkoff.kora.http.server.common.telemetry.HttpServerLogger; import ru.tinkoff.kora.http.server.common.telemetry.PrivateApiMetrics; -import ru.tinkoff.kora.logging.common.MDC; -import javax.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.time.Duration; diff --git a/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/handler/RequestHandlerUtils.java b/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/handler/RequestHandlerUtils.java index cdf955770..3df64ca86 100644 --- a/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/handler/RequestHandlerUtils.java +++ b/http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/handler/RequestHandlerUtils.java @@ -8,20 +8,20 @@ import javax.annotation.ParametersAreNonnullByDefault; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @ParametersAreNonnullByDefault -public class RequestHandlerUtils { - private RequestHandlerUtils() { - } +public final class RequestHandlerUtils { + + private RequestHandlerUtils() {} /* - * Path: String, UUID, Integer, Long, Double, Enum + * Path: String, UUID, Integer, Long, Double, Enum */ - @Nonnull public static String parseStringPathParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.pathParams().get(name); @@ -31,11 +31,14 @@ public static String parseStringPathParameter(HttpServerRequest request, String return result; } + @Nonnull public static UUID parseUUIDPathParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.pathParams().get(name); if (result == null) { throw HttpServerResponseException.of(400, "Path parameter '%s' is required".formatted(name)); } + + try { return UUID.fromString(result); } catch (IllegalArgumentException e) { @@ -48,6 +51,7 @@ public static int parseIntegerPathParameter(HttpServerRequest request, String na if (result == null) { throw HttpServerResponseException.of(400, "Path parameter '%s' is required".formatted(name)); } + try { return Integer.parseInt(result); } catch (NumberFormatException e) { @@ -60,6 +64,7 @@ public static long parseLongPathParameter(HttpServerRequest request, String name if (result == null) { throw HttpServerResponseException.of(400, "Path parameter '%s' is required".formatted(name)); } + try { return Long.parseLong(result); } catch (NumberFormatException e) { @@ -72,6 +77,7 @@ public static double parseDoublePathParameter(HttpServerRequest request, String if (result == null) { throw HttpServerResponseException.of(400, "Path parameter '%s' is required".formatted(name)); } + try { return Double.parseDouble(result); } catch (NumberFormatException e) { @@ -85,6 +91,7 @@ public static > T parseEnumPathParameter(HttpServerRequest req if (result == null) { throw HttpServerResponseException.of(400, "Path parameter '%s' is required".formatted(name)); } + try { return Enum.valueOf(enumType, result); } catch (Exception exception) { @@ -95,7 +102,6 @@ public static > T parseEnumPathParameter(HttpServerRequest req /* * Headers: String, Integer, List, List */ - @Nonnull public static String parseStringHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.headers().get(name); @@ -115,15 +121,26 @@ public static String parseOptionalStringHeaderParameter(HttpServerRequest reques } public static List parseStringListHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { - var result = request.headers().get(name); + var result = parseOptionalStringListHeaderParameter(request, name); if (result == null) { throw HttpServerResponseException.of(400, "Header '%s' is required".formatted(name)); } + + return result; + } + + @Nullable + public static List parseOptionalStringListHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = request.headers().get(name); + if (result == null) { + return null; + } + return result.stream() .flatMap(h -> Stream.of(h.split(","))) .map(String::trim) .filter(Predicate.not(String::isBlank)) - .collect(Collectors.toList()); + .toList(); } public static int parseIntegerHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { @@ -131,7 +148,12 @@ public static int parseIntegerHeaderParameter(HttpServerRequest request, String if (result == null || result.isEmpty()) { throw HttpServerResponseException.of(400, "Header '%s' is required".formatted(name)); } + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Header '%s' has invalid blank string value".formatted(name)); + } + try { return Integer.parseInt(first); } catch (NumberFormatException e) { @@ -145,7 +167,12 @@ public static Integer parseOptionalIntegerHeaderParameter(HttpServerRequest requ if (result == null || result.isEmpty()) { return null; } + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Header '%s' has invalid blank string value".formatted(name)); + } + try { return Integer.parseInt(first); } catch (NumberFormatException e) { @@ -154,20 +181,31 @@ public static Integer parseOptionalIntegerHeaderParameter(HttpServerRequest requ } public static List parseIntegerListHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalIntegerListHeaderParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Header '%s' is required".formatted(name)); + } + return result; + } + + @Nullable + public static List parseOptionalIntegerListHeaderParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var headers = request.headers().get(name); if (headers == null || headers.isEmpty()) { - throw HttpServerResponseException.of(400, "Header '%s' is required".formatted(name)); + return null; } + List result = new ArrayList<>(); - for (String header: headers) { + for (String header : headers) { header = header.trim(); if (!header.isEmpty()) { String[] split = header.split(","); - for (String s: split) { + for (String s : split) { s = s.trim(); if (s.isEmpty()) { throw HttpServerResponseException.of(400, "Header %s(%s) has invalid value".formatted(name, header)); } + try { result.add(Integer.parseInt(s)); } catch (NumberFormatException e) { @@ -177,22 +215,49 @@ public static List parseIntegerListHeaderParameter(HttpServerRequest re } } - return result; + return List.copyOf(result); } - /* - Query: String, Integer, Long, Double, Boolean, Enum, List, List, List, List, List, List>, UUID + * Query: String, Integer, Long, Double, Boolean, Enum, UUID */ - @Nonnull - public static String parseStringQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + public static UUID parseUuidQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalUuidQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + + return result; + } + + @Nullable + public static UUID parseOptionalUuidQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null || result.isEmpty()) { + return null; + } + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + + try { + return UUID.fromString(first); + } catch (IllegalArgumentException e) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid value '%s'".formatted(name, result)); + } + } + + @Nonnull + public static String parseStringQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalStringQueryParameter(request, name); + if (result == null) { throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } - return result.iterator().next(); + return result; } @Nullable @@ -201,24 +266,30 @@ public static String parseOptionalStringQueryParameter(HttpServerRequest request if (result == null || result.isEmpty()) { return null; } + return result.iterator().next(); } - public static List parseStringListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { - var result = request.queryParams().get(name); + public static int parseIntegerQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalIntegerQueryParameter(request, name); if (result == null) { - return List.of(); + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } - return List.copyOf(result); + return result; } - - public static int parseIntegerQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + @Nullable + public static Integer parseOptionalIntegerQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + return null; } - var first = result.iterator().next(); + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + try { return Integer.parseInt(first); } catch (NumberFormatException e) { @@ -226,186 +297,306 @@ public static int parseIntegerQueryParameter(HttpServerRequest request, String n } } + public static long parseLongQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalLongQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + return result; + } + @Nullable - public static Integer parseOptionalIntegerQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + public static Long parseOptionalLongQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null || result.isEmpty()) { return null; } - var first = result.iterator().next(); + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + try { - return Integer.parseInt(first); + return Long.parseLong(first); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); } } - public static List parseIntegerListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { - var result = request.queryParams().get(name); + @Nonnull + public static > T parseEnumQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { + var result = parseOptionalEnumQueryParameter(request, type, name); if (result == null) { - return List.of(); + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } - return result.stream() - .map(v -> { - if (v == null || v.isBlank()) { - return null; - } - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, v)); - } - }) - .collect(Collectors.toList()); + return result; } - - public static long parseLongQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + @Nullable + public static > T parseOptionalEnumQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null || result.isEmpty()) { + return null; + } + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + + try { + return Enum.valueOf(type, first); + } catch (Exception exception) { + throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, result)); + } + } + + public static boolean parseBooleanQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalBooleanQueryParameter(request, name); + if (result == null) { throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } - var first = result.iterator().next(); + return result; + } + + @Nullable + public static Boolean parseOptionalBooleanQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = request.queryParams().get(name); + if (result == null || result.isEmpty()) { + return null; + } + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + try { - return Long.parseLong(first); + // todo + return Boolean.parseBoolean(first); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); } } + public static double parseDoubleQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalDoubleQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + return result; + } + @Nullable - public static Long parseOptionalLongQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + public static Double parseOptionalDoubleQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null || result.isEmpty()) { return null; } - var first = result.iterator().next(); + + var first = result.iterator().next().trim(); + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + try { - return Long.parseLong(first); + return Double.parseDouble(first); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); } } - public static List parseLongListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + /* + * Query: List, List, List, List, List, List, List + */ + @Nonnull + public static List parseIntegerListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalIntegerListQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + return result; + } + + @Nullable + public static List parseOptionalIntegerListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null) { - return List.of(); + return null; } + return result.stream() + .filter(Objects::nonNull) .map(v -> { - if (v == null || v.isBlank()) { - return null; + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); } + try { - return Long.parseLong(v); + return Integer.parseInt(v); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, v)); } }) - .collect(Collectors.toList()); + .toList(); } + @Nonnull + public static List parseUuidListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalUuidListQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + return result; + } - public static double parseDoubleQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + @Nullable + public static List parseOptionalUuidListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); - if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + if (result == null) { + return null; } - var first = result.iterator().next(); - try { - return Double.parseDouble(first); - } catch (NumberFormatException e) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); + + return result.stream() + .filter(Objects::nonNull) + .map(v -> { + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + + try { + return UUID.fromString(v); + } catch (IllegalArgumentException e) { + throw HttpServerResponseException.of(400, "Path parameter '%s' has invalid value '%s'".formatted(name, result)); + } + }) + .toList(); + } + + @Nonnull + public static List parseStringListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalStringListQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } + return result; } @Nullable - public static Double parseOptionalDoubleQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + public static List parseOptionalStringListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); - if (result == null || result.isEmpty()) { + if (result == null) { return null; } - var first = result.iterator().next(); - try { - return Double.parseDouble(first); - } catch (NumberFormatException e) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); + + return result.stream().toList(); + } + + @Nonnull + public static List parseLongListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalLongListQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } + return result; } - public static List parseDoubleListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + @Nullable + public static List parseOptionalLongListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null) { - return List.of(); + return null; } + return result.stream() + .filter(Objects::nonNull) .map(v -> { - if (v == null || v.isBlank()) { - return null; + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); } + try { - return Double.parseDouble(v); + return Long.parseLong(v); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, v)); } }) - .collect(Collectors.toList()); + .toList(); } - - public static Boolean parseBooleanQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { - var result = request.queryParams().get(name); - if (result == null || result.isEmpty()) { + @Nonnull + public static List parseDoubleListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalDoubleListQueryParameter(request, name); + if (result == null) { throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } - var first = result.iterator().next(); - try { - // todo - return Boolean.parseBoolean(first); - } catch (NumberFormatException e) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); - } + return result; } @Nullable - public static Boolean parseOptionalBooleanQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + public static List parseOptionalDoubleListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); - if (result == null || result.isEmpty()) { + if (result == null) { return null; } - var first = result.iterator().next(); - try { - // todo - return Boolean.parseBoolean(first); - } catch (NumberFormatException e) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, first)); - } + + return result.stream() + .filter(Objects::nonNull) + .map(v -> { + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); + } + + try { + return Double.parseDouble(v); + } catch (NumberFormatException e) { + throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, v)); + } + }) + .toList(); } + @Nonnull public static List parseBooleanListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { + var result = parseOptionalBooleanListQueryParameter(request, name); + if (result == null) { + throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); + } + return result; + } + + @Nullable + public static List parseOptionalBooleanListQueryParameter(HttpServerRequest request, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); if (result == null) { - return List.of(); + return null; } + return result.stream() + .filter(Objects::nonNull) .map(v -> { - // todo - if (v == null || v.isBlank()) { - return null; + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); } + try { return Boolean.parseBoolean(v); } catch (NumberFormatException e) { throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, v)); } }) - .collect(Collectors.toList()); + .toList(); } - - public static > T parseEnumQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { - var result = parseOptionalEnumQueryParameter(request, type, name); + @Nonnull + public static > List parseEnumListQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { + var result = parseOptionalEnumListQueryParameter(request, type, name); if (result == null) { throw HttpServerResponseException.of(400, "Query parameter '%s' is required".formatted(name)); } @@ -413,29 +604,20 @@ public static > T parseEnumQueryParameter(HttpServerRequest re } @Nullable - public static > T parseOptionalEnumQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { + public static > List parseOptionalEnumListQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { var result = request.queryParams().get(name); - if (result == null || result.isEmpty()) { + if (result == null) { return null; } - var first = result.iterator().next(); - try { - return Enum.valueOf(type, first); - } catch (Exception exception) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".formatted(name, result)); - } - } - public static > List parseEnumListQueryParameter(HttpServerRequest request, Class type, String name) throws HttpServerResponseException { - var result = request.queryParams().get(name); - if (result == null) { - return List.of(); - } return result.stream() + .filter(Objects::nonNull) .map(v -> { - if (v == null || v.isBlank()) { - return null; + v = v.trim(); + if (v.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '%s' has invalid blank string value".formatted(name)); } + try { return Enum.valueOf(type, v); } catch (Exception exception) { @@ -444,5 +626,4 @@ public static > List parseEnumListQueryParameter(HttpServer }) .collect(Collectors.toList()); } - } diff --git a/http/http-server-common/src/main/kotlin/ru/tinkoff/kora/http/server/common/handler/ParameterExtractors.kt b/http/http-server-common/src/main/kotlin/ru/tinkoff/kora/http/server/common/handler/ParameterExtractors.kt index dd0697f61..334bb3f1a 100644 --- a/http/http-server-common/src/main/kotlin/ru/tinkoff/kora/http/server/common/handler/ParameterExtractors.kt +++ b/http/http-server-common/src/main/kotlin/ru/tinkoff/kora/http/server/common/handler/ParameterExtractors.kt @@ -3,104 +3,130 @@ package ru.tinkoff.kora.http.server.common.handler import ru.tinkoff.kora.http.server.common.HttpServerRequest import ru.tinkoff.kora.http.server.common.HttpServerResponseException import java.util.* -import java.util.stream.Collectors @Throws(HttpServerResponseException::class) fun extractStringPathParameter(request: HttpServerRequest, name: String): String { - return request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + return request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") } @Throws(HttpServerResponseException::class) fun extractUUIDPathParameter(request: HttpServerRequest, name: String): UUID { - val result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + var result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") + + result = result.trim() + if (result.isEmpty()) { + throw HttpServerResponseException.of(400, "Path parameter '$name' has invalid blank string value") + } + return try { UUID.fromString(result) } catch (e: IllegalArgumentException) { - throw HttpServerResponseException.of(400, "Path parameter '%s' has invalid value '%s'".format(name, result)) + throw HttpServerResponseException.of(400, "Path parameter '$name($result)' has invalid value") } } @Throws(HttpServerResponseException::class) fun extractIntPathParameter(request: HttpServerRequest, name: String): Int { - val result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + var result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") + + result = result.trim() + if (result.isEmpty()) { + throw HttpServerResponseException.of(400, "Path parameter '$name' has invalid blank string value") + } + return try { result.toInt() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Path parameter '%s' has invalid value '%s'".format(name, result)) + throw HttpServerResponseException.of(400, "Path parameter '$name($result)' has invalid value") } } @Throws(HttpServerResponseException::class) fun extractLongPathParameter(request: HttpServerRequest, name: String): Long { - val result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + var result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") + + result = result.trim() + if (result.isEmpty()) { + throw HttpServerResponseException.of(400, "Path parameter '$name' has invalid blank string value") + } + return try { result.toLong() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Path parameter %s(%s) has invalid value".format(name, result)) + throw HttpServerResponseException.of(400, "Path parameter '$name($result)' has invalid value") } } @Throws(HttpServerResponseException::class) fun extractDoublePathParameter(request: HttpServerRequest, name: String): Double { - val result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + var result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") + + result = result.trim() + if (result.isEmpty()) { + throw HttpServerResponseException.of(400, "Path parameter '$name' has invalid blank string value") + } + return try { result.toDouble() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Path parameter %s(%s) has invalid value".format(name, result)) + throw HttpServerResponseException.of(400, "Path parameter '$name($result)' has invalid value") } } @Throws(HttpServerResponseException::class) fun ?> extractEnumPathParameter(request: HttpServerRequest, enumType: Class?, name: String): T { - val result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '%s' is required".format(name)) + var result = request.pathParams()[name] ?: throw HttpServerResponseException.of(400, "Path parameter '$name' is required") + + result = result.trim() + if (result.isEmpty()) { + throw HttpServerResponseException.of(400, "Path parameter '$name' has invalid blank string value") + } + return try { java.lang.Enum.valueOf(enumType, result) } catch (exception: Exception) { - throw HttpServerResponseException.of(400, "Path parameter %s(%s) has invalid value".format(name, result)) + throw HttpServerResponseException.of(400, "Path parameter '$name($result)' has invalid value") } } /* - * Headers: String / List - */ - -/* - * Headers: String / List - */ + * Headers: String / List + */ @Throws(HttpServerResponseException::class) fun extractStringHeaderParameter(request: HttpServerRequest, name: String): String { - val result = request.headers()[name] ?: throw HttpServerResponseException.of(400, "Header '%s' is required".format(name)) + val result = request.headers()[name] ?: throw HttpServerResponseException.of(400, "Header '$name' is required") return result.joinToString(", ") } @Throws(HttpServerResponseException::class) fun extractNullableStringHeaderParameter(request: HttpServerRequest, name: String): String? { val result = request.headers()[name] - return if (result == null || result.isEmpty()) { - null - } else result.joinToString(", ") + return if (result.isNullOrEmpty()) null else result.joinToString(", ") } @Throws(HttpServerResponseException::class) fun extractStringListHeaderParameter(request: HttpServerRequest, name: String): List { - val result = request.headers()[name] ?: throw HttpServerResponseException.of(400, "Header '%s' is required".format(name)) + return extractNullableStringListHeaderParameter(request, name) ?: throw HttpServerResponseException.of(400, "Header '$name' is required") +} + +@Throws(HttpServerResponseException::class) +fun extractNullableStringListHeaderParameter(request: HttpServerRequest, name: String): List? { + val result = request.headers()[name] ?: return null + return result .flatMap { str -> str.split(",") } .map { obj -> obj.trim { it <= ' ' } } .filter { it.isNotBlank() } } -/* - Query: String, Int, Long, Double, Boolean, Enum, List, List, List, List, List, List> - */ /* - Query: String, Int, Long, Double, Boolean, Enum, List, List, List, List, List, List> - */ + * Query: String, Int, Long, Double, Boolean, Enum, List, List, List, List, List, List> + */ @Throws(HttpServerResponseException::class) fun extractStringQueryParameter(request: HttpServerRequest, name: String): String { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { + if (result.isNullOrEmpty()) { throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } return result.iterator().next() @@ -109,192 +135,252 @@ fun extractStringQueryParameter(request: HttpServerRequest, name: String): Strin @Throws(HttpServerResponseException::class) fun extractNullableStringQueryParameter(request: HttpServerRequest, name: String): String? { val result = request.queryParams()[name] - return if (result == null || result.isEmpty()) { - null - } else result.iterator().next() + return if (result.isNullOrEmpty()) null else result.iterator().next() } @Throws(HttpServerResponseException::class) fun extractStringListQueryParameter(request: HttpServerRequest, name: String): List { - val queryParams = request.queryParams() - val result = queryParams[name] ?: return emptyList() - return result.toMutableList().toList() + val result = request.queryParams()[name] ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") + return if (result.isEmpty()) emptyList() else result.toMutableList().toList() } +@Throws(HttpServerResponseException::class) +fun extractNullableStringListQueryParameter(request: HttpServerRequest, name: String): List? { + val result = request.queryParams()[name] + return if (result.isNullOrEmpty()) null else result.toMutableList().toList() +} @Throws(HttpServerResponseException::class) fun extractUUIDQueryParameter(request: HttpServerRequest, name: String): UUID { + return extractNullableUuidQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") +} + +@Throws(HttpServerResponseException::class) +fun extractNullableUuidQueryParameter(request: HttpServerRequest, name: String): UUID? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '$name' is required") + if (result.isNullOrEmpty()) { + return null + } + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") } - val first = result.iterator().next() + return try { UUID.fromString(first) } catch (e: IllegalArgumentException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } } +@Throws(HttpServerResponseException::class) +fun extractUuidListQueryParameter(request: HttpServerRequest, name: String): List { + return extractNullableUuidListQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") +} @Throws(HttpServerResponseException::class) -fun extractIntQueryParameter(request: HttpServerRequest, name: String): Int { +fun extractNullableUuidListQueryParameter(request: HttpServerRequest, name: String): List? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '$name' is required") - } - val first = result.iterator().next() - return try { - first.toInt() - } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) - } + return if (result.isNullOrEmpty()) null else result + .map { + val first = it.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + + try { + UUID.fromString(first) + } catch (e: IllegalArgumentException) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } + } + .toList() +} + +@Throws(HttpServerResponseException::class) +fun extractIntQueryParameter(request: HttpServerRequest, name: String): Int { + return extractNullableIntQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } @Throws(HttpServerResponseException::class) fun extractNullableIntQueryParameter(request: HttpServerRequest, name: String): Int? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { + if (result.isNullOrEmpty()) { return null } - val first = result.iterator().next() + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + return try { first.toInt() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } } @Throws(HttpServerResponseException::class) fun extractIntListQueryParameter(request: HttpServerRequest, name: String): List { - val result = request.queryParams()[name] ?: return emptyList() - return result - .mapNotNull { v -> - try { - v.toInt() - } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, v)) - } - } + return extractNullableIntListQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } +@Throws(HttpServerResponseException::class) +fun extractNullableIntListQueryParameter(request: HttpServerRequest, name: String): List? { + val result = request.queryParams()[name] ?: return null + return result.mapNotNull { v -> + val first = v.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + + try { + first.toInt() + } catch (e: NumberFormatException) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } + } +} @Throws(HttpServerResponseException::class) fun extractLongQueryParameter(request: HttpServerRequest, name: String): Long { - val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '$name' is required") - } - val first = result.iterator().next() - return try { - first.toLong() - } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) - } + return extractNullableLongQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } @Throws(HttpServerResponseException::class) fun extractNullableLongQueryParameter(request: HttpServerRequest, name: String): Long? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { + if (result.isNullOrEmpty()) { return null } - val first = result.iterator().next() + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + return try { first.toLong() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } } @Throws(HttpServerResponseException::class) -fun extractLongListQueryParameter(request: HttpServerRequest, name: String): List? { - val result = request.queryParams()[name] ?: return java.util.List.of() - return result.stream() - .map { v: String? -> - if (v == null || v.isBlank()) { - return@map null - } - try { - return@map v.toLong() - } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, v)) - } - } - .collect(Collectors.toList()) +fun extractLongListQueryParameter(request: HttpServerRequest, name: String): List { + return extractNullableLongListQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } +@Throws(HttpServerResponseException::class) +fun extractNullableLongListQueryParameter(request: HttpServerRequest, name: String): List? { + val result = request.queryParams()[name] ?: return null + return result.mapNotNull { v -> + val first = v.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + + try { + first.toLong() + } catch (e: NumberFormatException) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } + } +} @Throws(HttpServerResponseException::class) fun extractDoubleQueryParameter(request: HttpServerRequest, name: String): Double { - val result = request.queryParams()[name]?.first() ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") - return try { - result.toDouble() - } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, result)) - } + return extractNullableDoubleQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } @Throws(HttpServerResponseException::class) fun extractNullableDoubleQueryParameter(request: HttpServerRequest, name: String): Double? { - val result = request.queryParams()[name]?.first() + val result = request.queryParams()[name] + if (result.isNullOrEmpty()) { + return null + } + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + return try { - result?.toDouble() + first.toDouble() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, result)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } } @Throws(HttpServerResponseException::class) fun extractDoubleListQueryParameter(request: HttpServerRequest, name: String): List { - return request.queryParams()[name]?.mapNotNull { v: String? -> + return extractNullableDoubleListQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") +} + +@Throws(HttpServerResponseException::class) +fun extractNullableDoubleListQueryParameter(request: HttpServerRequest, name: String): List? { + val result = request.queryParams()[name] ?: return null + return result.mapNotNull { v -> + val first = v.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + try { - v?.toDouble() + first.toDouble() } catch (e: NumberFormatException) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, v)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } - } ?: return emptyList() + } } - @Throws(HttpServerResponseException::class) fun extractBooleanQueryParameter(request: HttpServerRequest, name: String): Boolean { - val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { - throw HttpServerResponseException.of(400, "Query parameter '$name' is required") - } - val first = result.iterator().next() - return if (first in listOf("true", "false")) { - first.toBoolean() - } else throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) + return extractNullableBooleanQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } @Throws(HttpServerResponseException::class) fun extractNullableBooleanQueryParameter(request: HttpServerRequest, name: String): Boolean? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { + if (result.isNullOrEmpty()) { return null } - val first = result.iterator().next() - return if (first in listOf("true", "false")) { + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + + return try { first.toBoolean() - } else throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, first)) + } catch (e: Exception) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } } @Throws(HttpServerResponseException::class) -fun extractBooleanListQueryParameter(request: HttpServerRequest, name: String): List? { - val result = request.queryParams()[name] ?: return emptyList() - return result - .map { v -> - if (v == null || v.isBlank()) { - return@map null - } - return@map if (v in listOf("true", "false")) { - v.toBoolean() - } else throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, v)) - } +fun extractBooleanListQueryParameter(request: HttpServerRequest, name: String): List { + return extractNullableBooleanListQueryParameter(request, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") } +@Throws(HttpServerResponseException::class) +fun extractNullableBooleanListQueryParameter(request: HttpServerRequest, name: String): List? { + val result = request.queryParams()[name] ?: return null + return result.mapNotNull { v -> + val first = v.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + + try { + first.toBoolean() + } catch (e: Exception) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } + } +} @Throws(HttpServerResponseException::class) fun ?> extractEnumQueryParameter(request: HttpServerRequest, type: Class?, name: String): T { @@ -304,26 +390,40 @@ fun ?> extractEnumQueryParameter(request: HttpServerRequest, type: C @Throws(HttpServerResponseException::class) fun ?> extractNullableEnumQueryParameter(request: HttpServerRequest, type: Class?, name: String): T? { val result = request.queryParams()[name] - if (result == null || result.isEmpty()) { + if (result.isNullOrEmpty()) { return null } - val first = result.iterator().next() + + val first = result.iterator().next().trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") + } + return try { java.lang.Enum.valueOf(type, first) } catch (exception: Exception) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, result)) + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") } } @Throws(HttpServerResponseException::class) fun ?> extractEnumListQueryParameter(request: HttpServerRequest, type: Class, name: String): List { - val result = request.queryParams()[name] ?: return emptyList() - return result - .mapNotNull { v: String? -> - try { - java.lang.Enum.valueOf(type, v) - } catch (exception: Exception) { - throw HttpServerResponseException.of(400, "Query parameter %s(%s) has invalid value".format(name, result)) - } + return extractNullableEnumListQueryParameter(request, type, name) ?: throw HttpServerResponseException.of(400, "Query parameter '$name' is required") +} + +@Throws(HttpServerResponseException::class) +fun ?> extractNullableEnumListQueryParameter(request: HttpServerRequest, type: Class, name: String): List? { + val result = request.queryParams()[name] ?: return null + return result.mapNotNull { v -> + val first = v.trim() + if (first.isEmpty()) { + throw HttpServerResponseException.of(400, "Query parameter '$name' has invalid blank string value") } + + try { + java.lang.Enum.valueOf(type, v) + } catch (e: Exception) { + throw HttpServerResponseException.of(400, "Query parameter '$name($first)' has invalid value") + } + } } diff --git a/http/http-server-common/src/testFixtures/java/ru/tinkoff/kora/http/server/common/HttpServerTestKit.java b/http/http-server-common/src/testFixtures/java/ru/tinkoff/kora/http/server/common/HttpServerTestKit.java index cd6b4559c..d298fb499 100644 --- a/http/http-server-common/src/testFixtures/java/ru/tinkoff/kora/http/server/common/HttpServerTestKit.java +++ b/http/http-server-common/src/testFixtures/java/ru/tinkoff/kora/http/server/common/HttpServerTestKit.java @@ -18,8 +18,8 @@ import ru.tinkoff.kora.application.graph.ValueOf; import ru.tinkoff.kora.common.liveness.LivenessProbe; import ru.tinkoff.kora.common.liveness.LivenessProbeFailure; -import ru.tinkoff.kora.common.readiness.ReadinessProbeFailure; import ru.tinkoff.kora.common.readiness.ReadinessProbe; +import ru.tinkoff.kora.common.readiness.ReadinessProbeFailure; import ru.tinkoff.kora.common.util.ByteBufferFluxInputStream; import ru.tinkoff.kora.common.util.ReactorUtils; import ru.tinkoff.kora.http.common.HttpHeaders; diff --git a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/ExtractorFunction.kt b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/ExtractorFunction.kt index bf5deaf1b..72440e5b6 100644 --- a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/ExtractorFunction.kt +++ b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/ExtractorFunction.kt @@ -12,22 +12,32 @@ enum class ExtractorFunction(val memberName: MemberName) { DOUBLE_PATH(MemberName(extractorsPackage, "extractDoublePathParameter")), STRING_HEADER(MemberName(extractorsPackage, "extractStringHeaderParameter")), - NULLABLE_STRING_HEADER(MemberName(extractorsPackage, "extractNullableStringHeaderParameter")), + STRING_NULLABLE_HEADER(MemberName(extractorsPackage, "extractNullableStringHeaderParameter")), LIST_STRING_HEADER(MemberName(extractorsPackage, "extractStringListHeaderParameter")), + LIST_STRING_NULLABLE_HEADER(MemberName(extractorsPackage, "extractNullableStringListHeaderParameter")), + UUID_QUERY(MemberName(extractorsPackage, "extractUuidQueryParameter")), + UUID_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableUuidQueryParameter")), + UUID_LIST_QUERY(MemberName(extractorsPackage, "extractUuidListQueryParameter")), + UUID_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableUuidListQueryParameter")), STRING_QUERY(MemberName(extractorsPackage, "extractStringQueryParameter")), - NULLABLE_STRING_QUERY(MemberName(extractorsPackage, "extractNullableStringQueryParameter")), - LIST_STRING_QUERY(MemberName(extractorsPackage, "extractStringListQueryParameter")), + STRING_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableStringQueryParameter")), + STRING_LIST_QUERY(MemberName(extractorsPackage, "extractStringListQueryParameter")), + STRING_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableStringListQueryParameter")), INT_QUERY(MemberName(extractorsPackage, "extractIntQueryParameter")), - NULLABLE_INT_QUERY(MemberName(extractorsPackage, "extractNullableIntQueryParameter")), - LIST_INT_QUERY(MemberName(extractorsPackage, "extractIntListQueryParameter")), + INT_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableIntQueryParameter")), + INT_LIST_QUERY(MemberName(extractorsPackage, "extractIntListQueryParameter")), + INT_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableIntListQueryParameter")), LONG_QUERY(MemberName(extractorsPackage, "extractLongQueryParameter")), - NULLABLE_LONG_QUERY(MemberName(extractorsPackage, "extractNullableLongQueryParameter")), - LIST_LONG_QUERY(MemberName(extractorsPackage, "extractLongListQueryParameter")), + LONG_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableLongQueryParameter")), + LONG_LIST_QUERY(MemberName(extractorsPackage, "extractLongListQueryParameter")), + LONG_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableLongListQueryParameter")), DOUBLE_QUERY(MemberName(extractorsPackage, "extractDoubleQueryParameter")), - NULLABLE_DOUBLE_QUERY(MemberName(extractorsPackage, "extractNullableDoubleQueryParameter")), - LIST_DOUBLE_QUERY(MemberName(extractorsPackage, "extractDoubleListQueryParameter")), + DOUBLE_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableDoubleQueryParameter")), + DOUBLE_LIST_QUERY(MemberName(extractorsPackage, "extractDoubleListQueryParameter")), + DOUBLE_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableDoubleListQueryParameter")), BOOLEAN_QUERY(MemberName(extractorsPackage, "extractBooleanQueryParameter")), - NULLABLE_BOOLEAN_QUERY(MemberName(extractorsPackage, "extractNullableBooleanQueryParameter")), - LIST_BOOLEAN_QUERY(MemberName(extractorsPackage, "extractBooleanListQueryParameter")), + BOOLEAN_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableBooleanQueryParameter")), + BOOLEAN_LIST_QUERY(MemberName(extractorsPackage, "extractBooleanListQueryParameter")), + BOOLEAN_LIST_NULLABLE_QUERY(MemberName(extractorsPackage, "extractNullableBooleanListQueryParameter")), } diff --git a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/HttpControllerProcessor.kt b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/HttpControllerProcessor.kt index 3bf95cefb..bfbcf6ff4 100644 --- a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/HttpControllerProcessor.kt +++ b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/HttpControllerProcessor.kt @@ -1,10 +1,17 @@ package ru.tinkoff.kora.http.server.symbol.procesor -import com.google.devtools.ksp.* +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.isAnnotationPresent import com.google.devtools.ksp.processing.* -import com.google.devtools.ksp.symbol.* -import com.squareup.kotlinpoet.* -import com.squareup.kotlinpoet.ksp.* +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.validate +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.addOriginatingKSFile +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.writeTo import ru.tinkoff.kora.common.Module import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.server.common.annotation.HttpController diff --git a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/RouteProcessor.kt b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/RouteProcessor.kt index 976b99d85..34e4d9f19 100644 --- a/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/RouteProcessor.kt +++ b/http/http-server-symbol-processor/src/main/kotlin/ru/tinkoff/kora/http/server/symbol/procesor/RouteProcessor.kt @@ -217,7 +217,11 @@ class RouteProcessor(private val resolver: Resolver) { when (val arg = listTypeArgument.resolve()) { resolver.builtIns.intType, resolver.builtIns.intType.makeNullable() -> { if (paramType == "Query") { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_INT_QUERY, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.INT_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.INT_LIST_QUERY, name, valueParameter)) + } return } } @@ -225,12 +229,20 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.stringType, resolver.builtIns.stringType.makeNullable() -> { when (paramType) { "Query" -> { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_STRING_QUERY, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_LIST_QUERY, name, valueParameter)) + } return } "Header" -> { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_STRING_HEADER, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_STRING_HEADER, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_STRING_NULLABLE_HEADER, name, valueParameter)) + } return } } @@ -238,21 +250,44 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.doubleType, resolver.builtIns.doubleType.makeNullable() -> { if (paramType == "Query") { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_DOUBLE_QUERY, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.DOUBLE_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.DOUBLE_LIST_QUERY, name, valueParameter)) + } return } } resolver.builtIns.booleanType, resolver.builtIns.booleanType.makeNullable() -> { if (paramType == "Query") { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_BOOLEAN_QUERY, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.BOOLEAN_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.BOOLEAN_LIST_QUERY, name, valueParameter)) + } return } } resolver.builtIns.longType, resolver.builtIns.longType.makeNullable() -> { if (paramType == "Query") { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LIST_LONG_QUERY, name, valueParameter)) + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LONG_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LONG_LIST_QUERY, name, valueParameter)) + } + return + } + } + + uuidType -> { + if (paramType == "Query") { + if (parameterType.isMarkedNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.UUID_LIST_NULLABLE_QUERY, name, valueParameter)) + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.UUID_LIST_QUERY, name, valueParameter)) + } return } } @@ -260,21 +295,41 @@ class RouteProcessor(private val resolver: Resolver) { else -> { val parameterName = valueParameter.name!!.asString() if (paramType == "Query") { + val extractor = if (parameterType.isMarkedNullable) { + ExtractorFunction.STRING_LIST_NULLABLE_QUERY + } else { + ExtractorFunction.STRING_LIST_QUERY + } + val nullChecker = if (parameterType.isMarkedNullable) { + "?" + } else { + "" + } funBuilder.addParameter("_${parameterName}StringParameterReader", stringParameterReader.parameterizedBy(arg.toTypeName())) funBuilder.addStatement( - "val %L = %M(_request, %S).map { _${parameterName}StringParameterReader.read(it) }", + "val %L = %M(_request, %S)$nullChecker.map { _${parameterName}StringParameterReader.read(it) }", parameterName, - ExtractorFunction.LIST_STRING_QUERY.memberName, + extractor.memberName, name ) return } if (paramType == "Header") { + val extractor = if (parameterType.isMarkedNullable) { + ExtractorFunction.LIST_STRING_NULLABLE_HEADER + } else { + ExtractorFunction.LIST_STRING_HEADER + } + val nullChecker = if (parameterType.isMarkedNullable) { + "?" + } else { + "" + } funBuilder.addParameter("_${parameterName}StringParameterReader", stringParameterReader.parameterizedBy(arg.toTypeName())) funBuilder.addStatement( - "val %L = %M(_request, %S).map { _${parameterName}StringParameterReader.read(it) }", + "val %L = %M(_request, %S)$nullChecker.map { _${parameterName}StringParameterReader.read(it) }", parameterName, - ExtractorFunction.LIST_STRING_HEADER.memberName, + extractor, name ) return @@ -288,7 +343,7 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.stringType, resolver.builtIns.stringType.makeNullable() -> { when (paramType) { "Query" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_STRING_QUERY, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_NULLABLE_QUERY, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_QUERY, name, valueParameter)) @@ -296,7 +351,7 @@ class RouteProcessor(private val resolver: Resolver) { } "Header" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_STRING_HEADER, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_NULLABLE_HEADER, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.STRING_HEADER, name, valueParameter)) @@ -313,7 +368,7 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.intType, resolver.builtIns.intType.makeNullable() -> { when (paramType) { "Query" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_INT_QUERY, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.INT_NULLABLE_QUERY, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.INT_QUERY, name, valueParameter)) @@ -330,7 +385,7 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.longType, resolver.builtIns.longType.makeNullable() -> { when (paramType) { "Query" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_LONG_QUERY, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LONG_NULLABLE_QUERY, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.LONG_QUERY, name, valueParameter)) @@ -347,7 +402,7 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.doubleType, resolver.builtIns.doubleType.makeNullable() -> { when (paramType) { "Query" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_DOUBLE_QUERY, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.DOUBLE_NULLABLE_QUERY, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.DOUBLE_QUERY, name, valueParameter)) @@ -364,7 +419,7 @@ class RouteProcessor(private val resolver: Resolver) { resolver.builtIns.booleanType, resolver.builtIns.booleanType.makeNullable() -> { when (paramType) { "Query" -> if (isNullable) { - funBuilder.addCode(generateParamDeclaration(ExtractorFunction.NULLABLE_BOOLEAN_QUERY, name, valueParameter)) + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.BOOLEAN_NULLABLE_QUERY, name, valueParameter)) return } else { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.BOOLEAN_QUERY, name, valueParameter)) @@ -375,6 +430,14 @@ class RouteProcessor(private val resolver: Resolver) { uuidType -> { when (paramType) { + "Query" -> if (isNullable) { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.UUID_NULLABLE_QUERY, name, valueParameter)) + return + } else { + funBuilder.addCode(generateParamDeclaration(ExtractorFunction.UUID_QUERY, name, valueParameter)) + return + } + "Path" -> { funBuilder.addCode(generateParamDeclaration(ExtractorFunction.UUID_PATH, name, valueParameter)) return @@ -390,7 +453,7 @@ class RouteProcessor(private val resolver: Resolver) { when (paramType) { "Query" -> { if (isNullable) { - funBuilder.addStatement("val %L = %M(_request, %S)?.let(%L::read)", parameterName, ExtractorFunction.NULLABLE_STRING_QUERY.memberName, name, readerParameterName) + funBuilder.addStatement("val %L = %M(_request, %S)?.let(%L::read)", parameterName, ExtractorFunction.STRING_NULLABLE_QUERY.memberName, name, readerParameterName) } else { funBuilder.addStatement("val %L = %L.read(%M(_request, %S))", parameterName, readerParameterName, ExtractorFunction.STRING_QUERY.memberName, name) } @@ -400,7 +463,7 @@ class RouteProcessor(private val resolver: Resolver) { "Header" -> { if (isNullable) { - funBuilder.addStatement("val %L = %M(_request, %S)?.let(%L::read)", parameterName, ExtractorFunction.NULLABLE_STRING_HEADER.memberName, name, readerParameterName) + funBuilder.addStatement("val %L = %M(_request, %S)?.let(%L::read)", parameterName, ExtractorFunction.STRING_NULLABLE_HEADER.memberName, name, readerParameterName) } else { funBuilder.addStatement("val %L = %L.read(%M(_request, %S))", parameterName, readerParameterName, ExtractorFunction.STRING_HEADER.memberName, name) } diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/MultipleParamsController.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/MultipleParamsController.kt index fb7a60298..4d3c5f02c 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/MultipleParamsController.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/MultipleParamsController.kt @@ -2,14 +2,12 @@ package ru.tinkoff.kora.http.server.symbol.processor.controllers import reactor.core.publisher.Mono import ru.tinkoff.kora.common.Mapping -import ru.tinkoff.kora.http.common.HttpHeaders import ru.tinkoff.kora.http.common.HttpMethod import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.server.common.HttpServerRequest +import ru.tinkoff.kora.http.server.common.HttpServerResponseException import ru.tinkoff.kora.http.server.common.annotation.HttpController import ru.tinkoff.kora.http.server.common.handler.HttpServerRequestMapper -import ru.tinkoff.kora.http.server.common.HttpServerResponseException -import java.nio.charset.StandardCharsets @HttpController open class MultipleParamsController { diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerHeaderParameters.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerHeaderParameters.kt index e8ed4bb72..0e28a85a1 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerHeaderParameters.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerHeaderParameters.kt @@ -4,7 +4,6 @@ import ru.tinkoff.kora.http.common.HttpMethod import ru.tinkoff.kora.http.common.annotation.Header import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.server.common.annotation.HttpController -import java.util.* @HttpController open class TestControllerHeaderParameters { diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerKotlinSuspendHandler.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerKotlinSuspendHandler.kt index bf09715b3..8966a0b5b 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerKotlinSuspendHandler.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerKotlinSuspendHandler.kt @@ -1,13 +1,11 @@ package ru.tinkoff.kora.http.server.symbol.processor.controllers import kotlinx.coroutines.delay -import kotlinx.coroutines.reactor.mono import ru.tinkoff.kora.http.common.HttpMethod.GET import ru.tinkoff.kora.http.common.annotation.Header import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.common.annotation.Query import ru.tinkoff.kora.http.server.common.annotation.HttpController -import kotlin.coroutines.CoroutineContext @HttpController open class TestControllerKotlinSuspendHandler { diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithCustomReaders.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithCustomReaders.kt index b53784d9d..2592e1bb7 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithCustomReaders.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithCustomReaders.kt @@ -11,9 +11,13 @@ import ru.tinkoff.kora.http.server.common.annotation.HttpController @HttpController open class TestControllerWithCustomReaders { @HttpRoute(method = HttpMethod.GET, path = "/test/{pathListEntity}") - fun test(@Query("queryEntity") queryList: List, @Path("pathListEntity") pathEntity: ReadableEntity, @Header("header-Entity") headerEntity: ReadableEntity? ): HttpServerResponseEntity { - val resultList = queryList + pathEntity - + fun test( + @Path("pathListEntity") pathEntity: ReadableEntity, + @Query("queryEntity") queryList: List?, + @Header("header-Entity") headerEntity: ReadableEntity? + ): HttpServerResponseEntity { + val query = queryList ?: emptyList() + val resultList = query + pathEntity return HttpServerResponseEntity(200, resultList.joinToString(", ") { it.string }) } } diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithMappers.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithMappers.kt index cab858cae..4574ca98e 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithMappers.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithMappers.kt @@ -8,10 +8,10 @@ import ru.tinkoff.kora.http.common.HttpMethod import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.server.common.HttpServerRequest import ru.tinkoff.kora.http.server.common.HttpServerResponse +import ru.tinkoff.kora.http.server.common.SimpleHttpServerResponse import ru.tinkoff.kora.http.server.common.annotation.HttpController import ru.tinkoff.kora.http.server.common.handler.HttpServerRequestMapper import ru.tinkoff.kora.http.server.common.handler.HttpServerResponseMapper -import ru.tinkoff.kora.http.server.common.SimpleHttpServerResponse import java.nio.charset.StandardCharsets @HttpController diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithResponseEntity.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithResponseEntity.kt index 939c777e7..482a754d5 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithResponseEntity.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/controllers/TestControllerWithResponseEntity.kt @@ -4,8 +4,8 @@ import kotlinx.coroutines.delay import ru.tinkoff.kora.http.common.HttpMethod import ru.tinkoff.kora.http.common.annotation.HttpRoute import ru.tinkoff.kora.http.common.annotation.Query -import ru.tinkoff.kora.http.server.common.annotation.HttpController import ru.tinkoff.kora.http.server.common.HttpServerResponseEntity +import ru.tinkoff.kora.http.server.common.annotation.HttpController @HttpController open class TestControllerWithResponseEntity { diff --git a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/server/HttpResponseAssert.kt b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/server/HttpResponseAssert.kt index 9c11d49cc..e63fb8567 100644 --- a/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/server/HttpResponseAssert.kt +++ b/http/http-server-symbol-processor/src/test/kotlin/ru/tinkoff/kora/http/server/symbol/processor/server/HttpResponseAssert.kt @@ -1,8 +1,8 @@ package ru.tinkoff.kora.http.server.symbol.processor.server -import reactor.core.publisher.Flux import org.assertj.core.api.AbstractByteArrayAssert import org.assertj.core.api.Assertions +import reactor.core.publisher.Flux import reactor.core.publisher.Mono import ru.tinkoff.kora.http.common.HttpHeaders import ru.tinkoff.kora.http.server.common.HttpServerResponse diff --git a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowHttpServer.java b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowHttpServer.java index bc52c4ad4..196eae5ea 100644 --- a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowHttpServer.java +++ b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowHttpServer.java @@ -5,7 +5,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.XnioWorker; -import reactor.core.Exceptions; import reactor.core.publisher.Mono; import ru.tinkoff.kora.application.graph.ValueOf; import ru.tinkoff.kora.common.readiness.ReadinessProbe; @@ -18,9 +17,6 @@ import javax.annotation.Nullable; import java.net.InetSocketAddress; import java.time.Duration; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; public class UndertowHttpServer implements HttpServer, ReadinessProbe { diff --git a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowModule.java b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowModule.java index a1168cdf8..a8a0a0a70 100644 --- a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowModule.java +++ b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowModule.java @@ -5,14 +5,12 @@ import org.xnio.XnioWorker; import reactor.core.publisher.Mono; import ru.tinkoff.kora.application.graph.LifecycleWrapper; -import ru.tinkoff.kora.application.graph.PromiseOf; import ru.tinkoff.kora.application.graph.ValueOf; import ru.tinkoff.kora.application.graph.Wrapped; import ru.tinkoff.kora.http.server.common.HttpServerConfig; import ru.tinkoff.kora.http.server.common.HttpServerModule; import ru.tinkoff.kora.http.server.common.PrivateApiHandler; -import javax.annotation.Nullable; import java.util.concurrent.TimeUnit; public interface UndertowModule extends HttpServerModule { diff --git a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateApiHandler.java b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateApiHandler.java index 61a5478d8..b472a4595 100644 --- a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateApiHandler.java +++ b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateApiHandler.java @@ -4,13 +4,10 @@ import io.undertow.util.SameThreadExecutor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import ru.tinkoff.kora.application.graph.PromiseOf; import ru.tinkoff.kora.http.server.common.PrivateApiHandler; -import javax.annotation.Nullable; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicBoolean; public class UndertowPrivateApiHandler { private final PrivateApiHandler privateApiHandler; diff --git a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateHttpServer.java b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateHttpServer.java index d61c2394c..77df1f47f 100644 --- a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateHttpServer.java +++ b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPrivateHttpServer.java @@ -4,7 +4,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.XnioWorker; -import reactor.core.Exceptions; import reactor.core.publisher.Mono; import ru.tinkoff.kora.application.graph.ValueOf; import ru.tinkoff.kora.common.util.ReactorUtils; @@ -15,8 +14,6 @@ import javax.annotation.Nullable; import java.net.InetSocketAddress; import java.time.Duration; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; public class UndertowPrivateHttpServer implements PrivateHttpServer { private static final Logger log = LoggerFactory.getLogger(UndertowPrivateHttpServer.class); diff --git a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPublicApiHandler.java b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPublicApiHandler.java index eb5414e12..f8fad2218 100644 --- a/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPublicApiHandler.java +++ b/http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowPublicApiHandler.java @@ -17,7 +17,6 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Predicate; public final class UndertowPublicApiHandler implements HttpHandler {