Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.koraframework.common.annotation.Tag;
import io.koraframework.http.client.common.request.HttpClientRequestMapper;
import io.koraframework.http.common.body.HttpBody;
import io.koraframework.http.common.body.HttpBodyOutput;
import io.koraframework.json.common.JsonWriter;
import io.koraframework.json.common.annotation.Json;

Expand All @@ -16,6 +17,11 @@ default HttpClientRequestMapper<byte[]> httpClientRequestByteArrayMapper() {
return (body) -> HttpBody.octetStream(body);
}

@DefaultComponent
default HttpClientRequestMapper<HttpBodyOutput> httpClientRequestBodyOutputMapper() {
return body -> body;
}

@DefaultComponent
default HttpClientRequestMapper<ByteBuffer> httpClientRequestByteBufferMapper() {
return (body) -> HttpBody.octetStream(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.koraframework.http.client.common.response.mapper.HttpClientEitherResponseMapper;
import io.koraframework.http.client.common.response.mapper.JsonHttpClientResponseMapper;
import io.koraframework.http.common.HttpResponseEntity;
import io.koraframework.http.common.body.HttpBodyInput;
import io.koraframework.json.common.JsonReader;
import io.koraframework.json.common.annotation.Json;

Expand Down Expand Up @@ -40,6 +41,11 @@ default HttpClientResponseMapper<ByteBuffer> httpClientResponseByteBufferMapper(
};
}

@DefaultComponent
default HttpClientResponseMapper<HttpBodyInput> httpClientResponseBodyInputMapper() {
return HttpClientResponse::body;
}

@DefaultComponent
default <T> HttpClientResponseMapper<HttpResponseEntity<T>> httpClientResponseEntityResponseMapper(HttpClientResponseMapper<T> mapper) {
return response -> HttpResponseEntity.of(response.code(), response.headers().toMutable(), mapper.apply(response));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.koraframework.http.server.common.request.mapper;

import io.koraframework.common.annotation.DefaultComponent;
import io.koraframework.http.common.body.HttpBodyInput;
import io.koraframework.http.common.form.FormMultipart;
import io.koraframework.http.common.form.FormUrlEncoded;
import io.koraframework.http.server.common.request.HttpServerRequest;
Expand All @@ -19,6 +20,11 @@ default HttpServerRequestMapper<HttpServerRequest> noopHttpServerRequestMapper()
return (r) -> r;
}

@DefaultComponent
default HttpServerRequestMapper<HttpBodyInput> httpBodyInputHttpServerRequestMapper() {
return HttpServerRequest::body;
}

@DefaultComponent
default HttpServerRequestMapper<ByteBuffer> byteBufferHttpServerRequestMapper() {
return (r) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.koraframework.common.annotation.DefaultComponent;
import io.koraframework.http.common.HttpResponseEntity;
import io.koraframework.http.common.body.HttpBody;
import io.koraframework.http.common.body.HttpBodyOutput;
import io.koraframework.http.server.common.response.HttpServerResponse;
import io.koraframework.http.server.common.response.HttpServerResponseMapper;
import io.koraframework.json.common.JsonWriter;
Expand All @@ -27,6 +28,11 @@ default HttpServerResponseMapper<byte[]> byteArrayHttpServerResponseMapper() {
return (request, r) -> HttpServerResponse.of(200, HttpBody.octetStream(r));
}

@DefaultComponent
default HttpServerResponseMapper<HttpBodyOutput> httpBodyOutputHttpServerResponseMapper() {
return (request, r) -> HttpServerResponse.of(200, r);
}

@DefaultComponent
default HttpServerResponseMapper<String> stringHttpServerResponseMapper() {
return (request, r) -> HttpServerResponse.of(200, HttpBody.plaintext(r));
Expand Down
1 change: 1 addition & 0 deletions openapi/openapi-generator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ sourceSets {
addOpenapiDir("petstoreV3_request_parameters")
addOpenapiDir("petstoreV3_responses")
addOpenapiDir("petstoreV3_same_response_model")
addOpenapiDir("petstoreV3_bare_object")
addOpenapiDir("petstoreV3_security_all")
addOpenapiDir("petstoreV3_security_all_auth_arg")
addOpenapiDir("petstoreV3_security_multi")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static class Classes {
public static final ClassName httpBody = ClassName.get("io.koraframework.http.common.body", "HttpBody");
public static final ClassName formMultipart = ClassName.get("io.koraframework.http.common.form", "FormMultipart");
public static final ClassName formPart = formMultipart.nestedClass("FormPart");
public static final ClassName httpBodyInput = ClassName.get("io.koraframework.http.common.body", "HttpBodyInput");
public static final ClassName httpBodyOutput = ClassName.get("io.koraframework.http.common.body", "HttpBodyOutput");

// Client
Expand Down Expand Up @@ -143,6 +144,9 @@ protected static String toVarName(String s) {
}

public TypeName asType(OperationsMap ctx, CodegenOperation operation, CodegenParameter param) {
if (param.isBodyParam && isBareObject(param) && params.rawBodyMode != CodegenParams.RawBodyMode.OBJECT) {
return requestBodyType();
}
if (param.getSchema() != null) {
return asType(param.getSchema());
}
Expand Down Expand Up @@ -186,6 +190,9 @@ public TypeName asType(IJsonSchemaValidationProperties schema) {
if (rs.isFile) {
return ArrayTypeName.of(TypeName.BYTE);
}
if (isBareObject(rs) && params.rawBodyMode != CodegenParams.RawBodyMode.OBJECT) {
return responseBodyType();
}
}
if (schema.getIsModel() && schema instanceof CodegenModel c) {
return ClassName.get(modelPackage, c.getClassname());
Expand All @@ -200,6 +207,9 @@ public TypeName asType(IJsonSchemaValidationProperties schema) {
return ParameterizedTypeName.get(ClassName.get(List.class), asType(schema.getItems()).box());
}
if (schema.getIsMap()) {
if (schema.getAdditionalProperties() == null) {
return ClassName.get(Object.class);
}
return ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(String.class), asType(schema.getAdditionalProperties()).box());
}
if (schema.getIsModel()) {
Expand Down Expand Up @@ -291,4 +301,40 @@ public TypeName asType(IJsonSchemaValidationProperties schema) {
}
throw new IllegalArgumentException(schema.toString());
}

protected TypeName requestBodyType() {
if (params.rawBodyMode == CodegenParams.RawBodyMode.BYTES) {
return ArrayTypeName.of(TypeName.BYTE);
}
if (params.rawBodyMode == CodegenParams.RawBodyMode.OBJECT) {
return ClassName.get(Object.class);
}
return params.codegenMode.isClient()
? Classes.httpBodyOutput
: Classes.httpBodyInput;
}

protected TypeName responseBodyType() {
if (params.rawBodyMode == CodegenParams.RawBodyMode.BYTES) {
return ArrayTypeName.of(TypeName.BYTE);
}
if (params.rawBodyMode == CodegenParams.RawBodyMode.OBJECT) {
return ClassName.get(Object.class);
}
return params.codegenMode.isClient()
? Classes.httpBodyInput
: Classes.httpBodyOutput;
}

protected boolean isBareObject(IJsonSchemaValidationProperties schema) {
return "Object".equals(schema.getDataType())
|| schema.getIsMap() && schema.getAdditionalProperties() == null
|| schema instanceof CodegenProperty p && p.isFreeFormObject
|| schema instanceof CodegenParameter cp && cp.isFreeFormObject
|| schema instanceof CodegenResponse r && r.isFreeFormObject;
}

protected boolean requiresJsonMapper(IJsonSchemaValidationProperties schema) {
return !isBareObject(schema) || params.rawBodyMode == CodegenParams.RawBodyMode.OBJECT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class CodegenParams {
public static final String IMPLICIT_HEADERS = "implicitHeaders";
public static final String IMPLICIT_HEADERS_REGEX = "implicitHeadersRegex";
public static final String FORCE_INCLUDE_OPTIONAL = "forceIncludeOptional";
public static final String RAW_BODY_MODE = "rawBodyMode";

public CodegenMode codegenMode = CodegenMode.JAVA_CLIENT;
public boolean enableValidation = false;
Expand All @@ -47,6 +48,7 @@ public class CodegenParams {
public boolean implicitHeaders = false;
public @Nullable Pattern implicitHeadersRegex = null;
public boolean forceIncludeOptional = false;
public RawBodyMode rawBodyMode = RawBodyMode.BYTES;

static List<CliOption> cliOptions() {
var cliOptions = new ArrayList<CliOption>();
Expand All @@ -65,6 +67,7 @@ static List<CliOption> cliOptions() {
cliOptions.add(CliOption.newString(PREFIX_PATH, "Path prefix for HTTP Server controllers"));
cliOptions.add(CliOption.newString(DELEGATE_METHOD_BODY_MODE, "Delegate method generation mode"));
cliOptions.add(CliOption.newString(FORCE_INCLUDE_OPTIONAL, "If enabled forces Nullable and NonRequired fields to be included ALWAYS even if null, can't be enabled with enableJsonNullable simultaneously"));
cliOptions.add(CliOption.newString(RAW_BODY_MODE, "Bare object request and response body mode (one of BYTES, BODY, OBJECT)"));
return cliOptions;
}

Expand Down Expand Up @@ -143,6 +146,19 @@ static CodegenParams parse(Map<String, Object> additionalProperties) {
if (additionalProperties.containsKey(FORCE_INCLUDE_OPTIONAL)) {
params.forceIncludeOptional = Boolean.parseBoolean(additionalProperties.get(FORCE_INCLUDE_OPTIONAL).toString());
}
if (additionalProperties.containsKey(RAW_BODY_MODE)) {
params.rawBodyMode = RawBodyMode.of(additionalProperties.get(RAW_BODY_MODE).toString());
}
return params;
}

public enum RawBodyMode {
BYTES,
BODY,
OBJECT;

public static RawBodyMode of(String value) {
return RawBodyMode.valueOf(value.toUpperCase(Locale.ROOT));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ protected ParameterSpec buildParameter(OperationsMap ctx, CodegenOperation opera
.addMember("value", "$S", param.baseName)
.build());
}
if (param.isBodyParam && KoraCodegen.isContentJson(param)) {
if (param.isBodyParam && KoraCodegen.isContentJson(param) && requiresJsonMapper(param)) {
b.addAnnotation(jsonAnnotation());
}
if (params.codegenMode.isServer() && params.enableValidation) {
Expand Down Expand Up @@ -208,6 +208,7 @@ protected CodeBlock buildMethodJavadoc(OperationsMap ctx, CodegenOperation opera
if (operation.notes != null) {
b.add(operation.notes).add("\n");
}
b.add("\n");
for (var param : operation.allParams) {
if (!param.isFormParam) {
b.add("@param ").add(param.paramName).add(" ");
Expand All @@ -228,16 +229,23 @@ protected CodeBlock buildMethodJavadoc(OperationsMap ctx, CodegenOperation opera
b.add("\n");
}
}
if (!operation.responses.isEmpty()) {
b.add("@return ");
for (var i = 0; i < operation.responses.size(); i++) {
if (i > 0) {
b.add("\n ");
}
var response = operation.responses.get(i);
b.add(Objects.requireNonNullElse(response.message, ""));
b.add(" (status code ");
b.add(response.isDefault ? "default" : response.code);
b.add(")");
}
b.add("\n");
}
if (operation.isDeprecated) {
b.add("@deprecated\n");
}
for (var response : operation.responses) {
b.add("@return ")
.add(response.message)
.add(" (status code ")
.add(response.code)
.add(")\n");
}
if (operation.externalDocs != null) {
b.add("@see <a href=\"" + operation.externalDocs.getUrl() + "\">" + operation.summary + " Documentation</a>");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private TypeSpec responseMapper(OperationsMap ctx, ClassName mappers, CodegenOpe
var mapperType = ParameterizedTypeName.get(Classes.httpClientResponseMapper, asType(response));
b.addField(mapperType, "delegate", Modifier.PRIVATE, Modifier.FINAL);
var mapperParam = ParameterSpec.builder(mapperType, "delegate");
if (isContentJson(response.getContent())) {
if (isContentJson(response.getContent()) && requiresJsonMapper(response)) {
mapperParam.addAnnotation(jsonAnnotation());
}
constructor = MethodSpec.constructorBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private TypeSpec buildMapper(OperationsMap ctx, CodegenOperation operation) {
var mapperName = "response" + response.code + "Delegate";
b.addField(mapperType, mapperName, Modifier.PRIVATE, Modifier.FINAL);
var param = ParameterSpec.builder(mapperType, mapperName);
if (KoraCodegen.isContentJson(response.getContent())) {
if (KoraCodegen.isContentJson(response.getContent()) && requiresJsonMapper(response)) {
param.addAnnotation(Classes.json);
}
constructor.addParameter(param.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ abstract class AbstractKotlinGenerator<C : Any> : AbstractGenerator<C, FileSpec>
.build()
)

param.isBodyParam && KoraCodegen.isContentJson(param) -> b.addAnnotation(
param.isBodyParam && KoraCodegen.isContentJson(param) && requiresJsonMapper(param) -> b.addAnnotation(
AnnotationSpec.builder(Classes.json.asKt())
.build()
)
Expand Down Expand Up @@ -257,12 +257,18 @@ abstract class AbstractKotlinGenerator<C : Any> : AbstractGenerator<C, FileSpec>
if (operation.isDeprecated) {
b.add("@deprecated\n")
}
for (response in operation.responses) {
if (operation.responses.isNotEmpty()) {
b.add("@return ")
.add(response.message)
.add(" (status code ")
.add(response.code)
.add(")\n")
for ((index, response) in operation.responses.withIndex()) {
if (index > 0) {
b.add("\n ")
}
b.add(response.message ?: "")
.add(" (status code ")
.add(if (response.isDefault) "default" else response.code)
.add(")")
}
b.add("\n")
}
if (operation.externalDocs != null) {
b.add("@see <a href=\"" + operation.externalDocs.url + "\">" + operation.summary + " Documentation</a>")
Expand All @@ -275,13 +281,17 @@ abstract class AbstractKotlinGenerator<C : Any> : AbstractGenerator<C, FileSpec>
com.palantir.javapoet.ClassName.get("java.util", "Map") -> Map::class.asClassName()
com.palantir.javapoet.ClassName.get("java.util", "Set") -> Set::class.asClassName()
com.palantir.javapoet.ClassName.get("java.lang", "String") -> String::class.asClassName()
com.palantir.javapoet.ClassName.get("java.lang", "Object") -> ANY
com.palantir.javapoet.ClassName.get("java.math", "BigDecimal") -> java.math.BigDecimal::class.asClassName()
com.palantir.javapoet.ClassName.get("io.koraframework.http.common.body", "HttpBodyInput") -> ClassName("io.koraframework.http.common.body", "HttpBodyInput")
com.palantir.javapoet.ClassName.get("io.koraframework.http.common.body", "HttpBodyOutput") -> ClassName("io.koraframework.http.common.body", "HttpBodyOutput")
com.palantir.javapoet.TypeName.INT.box() -> INT
com.palantir.javapoet.TypeName.LONG.box() -> LONG
com.palantir.javapoet.TypeName.SHORT.box() -> SHORT
com.palantir.javapoet.TypeName.BYTE.box() -> BYTE
com.palantir.javapoet.TypeName.DOUBLE.box() -> DOUBLE
com.palantir.javapoet.TypeName.FLOAT.box() -> FLOAT
com.palantir.javapoet.TypeName.BOOLEAN.box() -> BOOLEAN
com.palantir.javapoet.TypeName.FLOAT.box() -> BOOLEAN
com.palantir.javapoet.TypeName.BOOLEAN.box() -> FLOAT
else -> ClassName(packageName(), simpleNames())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ClientResponseMapperGenerator : AbstractKotlinGenerator<OperationsMap>() {
val mapperType = Classes.httpClientResponseMapper.asKt().parameterizedBy(asType(response).asKt())
b.addProperty(PropertySpec.builder("delegate", mapperType).initializer("delegate").build())
val mapperParam = ParameterSpec.builder("delegate", mapperType)
if (KoraCodegen.isContentJson(response.content)) {
if (KoraCodegen.isContentJson(response.content) && requiresJsonMapper(response)) {
mapperParam.addAnnotation(jsonAnnotation())
}
constructor.addParameter(mapperParam.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,11 @@ class ModelGenerator : AbstractKotlinGenerator<ModelsMap>() {
enumModel.isString = enumSource.isString
enumModel.isLong = enumSource.isLong
enumModel.isInteger = enumSource.isInteger
enumModel.isDouble = enumSource.isDouble
enumModel.isFloat = enumSource.isFloat
enumModel.isBoolean = enumSource.isBoolean
enumModel.isFloat = enumSource.isFloat
enumModel.isDouble = enumSource.isDouble
enumModel.isDecimal = enumSource.isDecimal
enumModel.isNumber = enumSource.isNumber
val enumTypeSpec = buildEnum(ctx, enumModel)
b.addType(enumTypeSpec)
fieldType = ClassName(modelPackage, model.getClassname(), enumModel.name)
Expand Down Expand Up @@ -386,6 +388,9 @@ class ModelGenerator : AbstractKotlinGenerator<ModelsMap>() {
if (model.isInteger || "Integer" == model.dataType) {
return INT
}
if ("BigDecimal" == model.dataType) {
return java.math.BigDecimal::class.asClassName()
}
if (model.isDouble || "Double" == model.dataType) {
return DOUBLE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ServerResponseMappersGenerator : AbstractKotlinGenerator<OperationsMap>()
val mapperName = "response" + response.code + "Delegate"
b.addProperty(PropertySpec.builder(mapperName, mapperType).initializer(mapperName).build())
val param = ParameterSpec.builder(mapperName, mapperType)
if (KoraCodegen.isContentJson(response.content)) {
if (KoraCodegen.isContentJson(response.content) && requiresJsonMapper(response)) {
param.addAnnotation(Classes.json.asKt())
}
constructor.addParameter(param.build())
Expand Down
Loading
Loading