Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions bin/configs/kotlin-model-prefix-type-mapping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ additionalProperties:
library: jvm-retrofit2
enumPropertyNaming: UPPERCASE
serializationLibrary: gson
enableOneOfWrapper: true
openapiNormalizer:
SIMPLIFY_ONEOF_ANYOF: false
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.samskivert.mustache.Mustache;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
Expand Down Expand Up @@ -513,6 +514,14 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("auth/HttpBasicAuth.kt.mustache", authFolder, "HttpBasicAuth.kt"));
}
}

additionalProperties.put("sanitizeGeneric", (Mustache.Lambda) (fragment, writer) -> {
String content = fragment.execute();
for (final String s: List.of("<", ">", ",", " ")) {
content = content.replace(s, "");
}
writer.write(content);
});
}

private void processDateLibrary() {
Expand Down Expand Up @@ -869,10 +878,11 @@ public ModelsMap postProcessModels(ModelsMap objs) {

for (ModelMap mo : objects.getModels()) {
CodegenModel cm = mo.getModel();
if (getGenerateRoomModels()) {
if (getGenerateRoomModels() || Boolean.parseBoolean(String.valueOf(additionalProperties.get("enableOneOfWrapper")))) {
cm.vendorExtensions.put("x-has-data-class-body", true);
}


// escape the variable base name for use as a string literal
List<CodegenProperty> vars = Stream.of(
cm.vars,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ This runs all tests and packages the library.

All URIs are relative to *{{{basePath}}}*

Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{{summary}}}
| Class | Method | HTTP request | Description |
| ------------ | ------------- | ------------- | ------------- |
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}| *{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{{summary}}} |
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
{{/generateApiDocs}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

All URIs are relative to *{{basePath}}*

Method | HTTP request | Description
------------- | ------------- | -------------
{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}}
| Method | HTTP request | Description |
| ------------- | ------------- | ------------- |
{{#operations}}{{#operation}}| [**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} |
{{/operation}}{{/operations}}

{{#operations}}
Expand Down Expand Up @@ -42,10 +42,15 @@ try {
```

### Parameters
{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}}
{{^allParams}}
This endpoint does not need any parameter.
{{/allParams}}
{{#allParams}}
{{#-last}}
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
{{/-last}}
| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} |
{{/allParams}}

### Return type
Expand Down Expand Up @@ -84,4 +89,4 @@ Configure {{name}}:
- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}

{{/operation}}
{{/operations}}
{{/operations}}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ version '{{artifactVersion}}'
{{^omitGradleWrapper}}

wrapper {
gradleVersion = '7.5'
gradleVersion = '7.6.4'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
{{/omitGradleWrapper}}

buildscript {
ext.kotlin_version = '1.8.10'
ext.kotlin_version = '1.9.10'
{{#jvm-ktor}}
ext.ktor_version = '2.3.9'
{{/jvm-ktor}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# {{classname}}

## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#isReadOnly}} [readonly]{{/isReadOnly}}
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
{{#vars}}| **{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#isReadOnly}} [readonly]{{/isReadOnly}} |
{{/vars}}
{{#vars}}{{#isEnum}}

<a id="{{{datatypeWithEnum}}}"></a>{{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}}
## Enum: {{baseName}}
Name | Value
---- | -----{{#allowableValues}}
{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}
| Name | Value |
| ---- | ----- |{{#allowableValues}}
| {{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}} |
{{/isEnum}}{{/vars}}
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{{^multiplatform}}
{{#gson}}
{{#enableOneOfWrapper}}
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.annotations.JsonAdapter
{{/enableOneOfWrapper}}
import com.google.gson.annotations.SerializedName
{{/gson}}
{{#moshi}}
Expand Down Expand Up @@ -47,6 +57,8 @@ import java.io.Serializable
import {{roomModelPackage}}.{{classname}}RoomModel
import {{packageName}}.infrastructure.ITransformForStorage
{{/generateRoomModels}}
import java.io.IOException


/**
* {{{description}}}
Expand Down Expand Up @@ -128,7 +140,9 @@ import {{packageName}}.infrastructure.ITransformForStorage
{{/multiplatform}}
{{/enumVars}}
{{/allowableValues}}
}{{#kotlinx_serialization}}{{#enumUnknownDefaultCase}}
}
{{#kotlinx_serialization}}
{{#enumUnknownDefaultCase}}

@Serializer(forClass = {{{nameInPascalCase}}}::class)
internal object {{nameInPascalCase}}Serializer : KSerializer<{{nameInPascalCase}}> {
Expand All @@ -143,10 +157,217 @@ import {{packageName}}.infrastructure.ITransformForStorage
override fun serialize(encoder: Encoder, value: {{nameInPascalCase}}) {
encoder.encodeSerializableValue({{{dataType}}}.serializer(), value.value)
}
}{{/enumUnknownDefaultCase}}{{/kotlinx_serialization}}
}
{{/enumUnknownDefaultCase}}
{{/kotlinx_serialization}}
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
{{#enableOneOfWrapper}}

class CustomTypeAdapterFactory : TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (!{{classname}}::class.java.isAssignableFrom(type.rawType)) {
return null // this class only serializes '{{classname}}' and its subtypes
}
val elementAdapter = gson.getAdapter(JsonElement::class.java)
val thisAdapter = gson.getDelegateAdapter(this, TypeToken.get({{classname}}::class.java))

@Suppress("UNCHECKED_CAST")
return object : TypeAdapter<{{classname}}>() {
@Throws(IOException::class)
override fun write(out: JsonWriter, value: {{classname}}) {
val obj = thisAdapter.toJsonTree(value).getAsJsonObject()
elementAdapter.write(out, obj)
}

@Throws(IOException::class)
override fun read(jsonReader: JsonReader): {{classname}} {
val jsonElement = elementAdapter.read(jsonReader)
validateJsonElement(jsonElement)
return thisAdapter.fromJsonTree(jsonElement)
}
}.nullSafe() as TypeAdapter<T>
}
}

companion object {
var openapiFields: HashSet<String>? = null
var openapiRequiredFields: HashSet<String>? = null

init {
// a set of all properties/fields (JSON key names)
openapiFields = HashSet()
{{#allVars}}
openapiFields!!.add("{{baseName}}")
{{/allVars}}

// a set of required properties/fields (JSON key names)
openapiRequiredFields = HashSet()
{{#requiredVars}}
openapiRequiredFields!!.add("{{baseName}}")
{{/requiredVars}}
}

/**
* Validates the JSON Element and throws an exception if issues found
*
* @param jsonElement JSON Element
* @throws IOException if the JSON Element is invalid with respect to {{classname}}
*/
@Throws(IOException::class)
fun validateJsonElement(jsonElement: JsonElement?) {
if (jsonElement == null) {
require(openapiRequiredFields!!.isEmpty()) { // has required fields but JSON element is null
String.format("The required field(s) %s in {{{classname}}} is not found in the empty JSON string", {{classname}}.openapiRequiredFields.toString())
}
}
{{^hasChildren}}
{{^isAdditionalPropertiesTrue}}

// TODO
//val entries = jsonElement!!.getAsJsonObject().entrySet()
// check to see if the JSON string contains additional fields
//for ((key) in entries) {
// require(openapiFields!!.contains(key)) {
// String.format("The field `%s` in the JSON string is not defined in the `{{classname}}` properties. JSON: %s", key, jsonElement.toString())
// }
//}
{{/isAdditionalPropertiesTrue}}
{{#requiredVars}}
{{#-first}}

// check to make sure all required properties/fields are present in the JSON string
for (requiredField in openapiRequiredFields!!) {
requireNotNull(jsonElement!!.getAsJsonObject()[requiredField]) {
String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())
}
}
{{/-first}}
{{/requiredVars}}
{{/hasChildren}}
{{^discriminator}}
{{#hasVars}}
val jsonObj = jsonElement!!.getAsJsonObject()
{{/hasVars}}
{{#vars}}
{{#isArray}}
{{#items.isModel}}
{{#required}}
// ensure the json data is an array
if (!jsonObj.get("{{{baseName}}}").isJsonArray) {
throw new IllegalArgumentException(String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString()))
}

JsonArray jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}")
// validate the required field `{{{baseName}}}` (array)
for (i in 0 until jsonArray{{name}}.size()) {
{{{items.dataType}}}.validateJsonElement(jsonArray{{name}}.get(i))
}
{{/required}}
{{^required}}
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
val jsonArray{{name}} = jsonObj.getAsJsonArray("{{{baseName}}}")
if (jsonArray{{name}} != null) {
// ensure the json data is an array
require(jsonObj["{{{baseName}}}"].isJsonArray) {
String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}

// validate the optional field `{{{baseName}}}` (array)
for (i in 0 until jsonArray{{name}}.size()) {
{{{items.dataType}}}.validateJsonElement(jsonArray{{name}}[i])
}
}
}
{{/required}}
{{/items.isModel}}
{{^items.isModel}}
{{^required}}
// ensure the optional json data is an array if present
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
require(jsonObj["{{{baseName}}}"].isJsonArray()) {
String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
}
{{/required}}
{{#required}}
// ensure the required json array is present
requireNotNull(jsonObj["{{{baseName}}}"]) {
"Expected the field `{{{baseName}}}` to be an array in the JSON string but got `null`"
}
require(jsonObj["{{{baseName}}}"].isJsonArray()) {
String.format("Expected the field `{{{baseName}}}` to be an array in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
{{/required}}
{{/items.isModel}}
{{/isArray}}
{{^isContainer}}
{{#isString}}
{{#notRequiredOrIsNullable}}
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
require(jsonObj.get("{{{baseName}}}").isJsonPrimitive) {
String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
}
{{/notRequiredOrIsNullable}}
{{^notRequiredOrIsNullable}}
require(jsonObj["{{{baseName}}}"].isJsonPrimitive) {
String.format("Expected the field `{{{baseName}}}` to be a primitive type in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
{{/notRequiredOrIsNullable}}
{{/isString}}
{{#isModel}}
{{#required}}
// validate the required field `{{{baseName}}}`
{{{dataType}}}.validateJsonElement(jsonObj["{{{baseName}}}"])
{{/required}}
{{^required}}
// validate the optional field `{{{baseName}}}`
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
{{{dataType}}}.validateJsonElement(jsonObj["{{{baseName}}}"])
}
{{/required}}
{{/isModel}}
{{#isEnum}}
{{#required}}
// validate the required field `{{{baseName}}}`
require({{{datatypeWithEnum}}}.values().any { it.value == jsonObj["{{{baseName}}}"].asString }) {
String.format("Expected the field `{{{baseName}}}` to be valid `{{{datatypeWithEnum}}}` enum value in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
{{/required}}
{{^required}}
// validate the optional field `{{{baseName}}}`
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
require({{{datatypeWithEnum}}}.values().any { it.value == jsonObj["{{{baseName}}}"].asString }) {
String.format("Expected the field `{{{baseName}}}` to be valid `{{{datatypeWithEnum}}}` enum value in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
}
{{/required}}
{{/isEnum}}
{{#isEnumRef}}
{{#required}}
// validate the required field `{{{baseName}}}`
require({{{dataType}}}.values().any { it.value == jsonObj["{{{baseName}}}"].asString }) {
String.format("Expected the field `{{{baseName}}}` to be valid `{{{dataType}}}` enum value in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
{{/required}}
{{^required}}
// validate the optional field `{{{baseName}}}`
if (jsonObj["{{{baseName}}}"] != null && !jsonObj["{{{baseName}}}"].isJsonNull) {
require({{{dataType}}}.values().any { it.value == jsonObj["{{{baseName}}}"].asString }) {
String.format("Expected the field `{{{baseName}}}` to be valid `{{{dataType}}}` enum value in the JSON string but got `%s`", jsonObj["{{{baseName}}}"].toString())
}
}
{{/required}}
{{/isEnumRef}}
{{/isContainer}}
{{/vars}}
{{/discriminator}}
}
}
{{/enableOneOfWrapper}}

{{#vendorExtensions.x-has-data-class-body}}
}
{{/vendorExtensions.x-has-data-class-body}}
Loading