diff --git a/src/main/java/ru/touchin/codegen/TINetworkingCodegen.java b/src/main/java/ru/touchin/codegen/TINetworkingCodegen.java index 177dda1..967639e 100644 --- a/src/main/java/ru/touchin/codegen/TINetworkingCodegen.java +++ b/src/main/java/ru/touchin/codegen/TINetworkingCodegen.java @@ -6,6 +6,9 @@ import io.swagger.codegen.v3.*; import io.swagger.codegen.v3.generators.handlebars.BaseItemsHelper; import io.swagger.codegen.v3.generators.handlebars.ExtensionHelper; import io.swagger.codegen.v3.generators.kotlin.AbstractKotlinCodegen; +import io.swagger.v3.oas.models.media.DateSchema; +import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.Schema; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,8 +25,8 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { protected String dateLibrary = DateLibrary.JODA_TIME.value; protected String projectName = "SwaggerAPI"; private Map allCustomDateFormats = new HashMap<>(); - private Set allEnumAdapters = new HashSet<>(); - private Set allEnumAdaptersImports = new HashSet<>(); + private Set allJsonAdapters = new HashSet<>(); + private Set allJsonAdaptersImports = new HashSet<>(); public enum DateLibrary { STRING("string"), @@ -94,12 +97,12 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { supportingFiles.add(new SupportingFile("APIDateFormat.mustache", sourceFolder, "APIDateFormat.kt")); - supportingFiles.add(new SupportingFile("GeneratedDateAdapter.mustache", + supportingFiles.add(new SupportingFile("JsonAdapters.mustache", sourceFolder, - "GeneratedDateAdapter.kt")); - supportingFiles.add(new SupportingFile("EnumJsonAdapters.mustache", + "JsonAdapters.kt")); + supportingFiles.add(new SupportingFile("AuthorizationInterceptor.mustache", sourceFolder, - "EnumJsonAdapters.kt")); + "AuthorizationInterceptor.kt")); return supportingFiles; } @@ -152,8 +155,8 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { public Map postProcessSupportingFileData(Map objs) { Map supportingFileData = super.postProcessSupportingFileData(objs); supportingFileData.put("apiDateFormats", allCustomDateFormats); - supportingFileData.put("enumAdapters", allEnumAdapters); - supportingFileData.put("enumAdaptersImports", allEnumAdaptersImports); + supportingFileData.put("jsonAdapters", allJsonAdapters); + supportingFileData.put("jsonAdaptersImports", allJsonAdaptersImports); return supportingFileData; } @@ -161,32 +164,51 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { super.postProcessModelProperty(model, property); - updateEnumAdapters(model, property); + if (typeAliases.containsKey(property.baseType)) { + Schema resolvedPropertySchema = getOpenAPI().getComponents().getSchemas().get(property.datatype); + + postProcessAliasProperty(property, resolvedPropertySchema); + } + + addEnumJsonAdapters(model, property); updateVendorExtensionsForProperty(property); if (property.getIsListContainer()) { updateVendorExtensionsForProperty(property.items); } + if (isDateFormatProperty(property)) { + model.getVendorExtensions().put(TINetworkingCodegenConstants.HAS_DATE, Boolean.TRUE); + addJsonAdaptersForModelWithDate(model); + } + if (property.getIsObject() && isReservedWord(property.getDatatype())) { property.datatype = escapeReservedTypeDeclaration(property.getDatatype()); property.datatypeWithEnum = property.datatype; } } - private void updateEnumAdapters(CodegenModel model, CodegenProperty property) { + private void addEnumJsonAdapters(CodegenModel model, CodegenProperty property) { boolean isEnum = ExtensionHelper.getBooleanValue(property, "x-is-enum"); if (isEnum) { String enumAdapterName = model.name + "." + property.enumName + ".JsonAdapter"; String importName = modelPackage + "." + model.name; - allEnumAdapters.add(enumAdapterName); - allEnumAdaptersImports.add(importName); + allJsonAdapters.add(enumAdapterName); + allJsonAdaptersImports.add(importName); } } + private void addJsonAdaptersForModelWithDate(CodegenModel model) { + String jsonAdapterName = model.name + "." + model.name + "JsonAdapter"; + String importName = modelPackage + "." + model.name; + allJsonAdapters.add(jsonAdapterName); + allJsonAdaptersImports.add(importName); + + } + @Override public String toParamName(String name) { - return super.toParamName(name).replaceAll("[^A-Za-z0-9_]", ""); + return replaceSpecialCharacters(super.toParamName(name)); } @Override @@ -216,16 +238,21 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { if (isISO8601DateProperty(property)) { vendorExtensions.put(TINetworkingCodegenConstants.IS_ISO8601_DATE, true); + vendorExtensions.put(TINetworkingCodegenConstants.IS_DATE_FORMAT, true); } else if (isCustomDateFormatProperty(property)) { String customDateFormat = (String) vendorExtensions.get(TINetworkingCodegenConstants.DATE_FORMAT); - String dateFormatName = customDateFormat.replace(".", "_") - .replaceAll("[^A-Za-z0-9_]", ""); + String dateFormatName = replaceSpecialCharacters(customDateFormat.replace(".", "_")); vendorExtensions.put(TINetworkingCodegenConstants.DATE_FORMAT_NAME, dateFormatName); + vendorExtensions.put(TINetworkingCodegenConstants.IS_DATE_FORMAT, true); allCustomDateFormats.put(dateFormatName, customDateFormat); } } + private String replaceSpecialCharacters(String text) { + return text.replaceAll("[^A-Za-z0-9_]", ""); + } + private boolean isISO8601DateProperty(CodegenProperty property) { return property.getIsDate() || property.getIsDateTime() @@ -235,4 +262,33 @@ public class TINetworkingCodegen extends AbstractKotlinCodegen { private boolean isCustomDateFormatProperty(CodegenProperty property) { return property.getVendorExtensions().containsKey(TINetworkingCodegenConstants.DATE_FORMAT); } + + private boolean isDateFormatProperty(CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + return vendorExtensions.containsKey(TINetworkingCodegenConstants.DATE_FORMAT) + || property.getIsDate() + || property.getIsDateTime(); + } + + private void postProcessAliasProperty(CodegenProperty codegenProperty, Schema resolvedPropertySchema) { + Map propertyExtensions = codegenProperty.getVendorExtensions(); + + propertyExtensions.put(CodegenConstants.IS_ALIAS_EXT_NAME, Boolean.TRUE); + + codegenProperty.setDescription(codegenProperty.getDescription() == null + ? resolvedPropertySchema.getDescription() + : codegenProperty.getDescription()); + + if (resolvedPropertySchema.getExtensions() != null) { + propertyExtensions.putAll(resolvedPropertySchema.getExtensions()); + } + + if (resolvedPropertySchema instanceof DateSchema) { + propertyExtensions.put(CodegenConstants.IS_DATE_EXT_NAME, Boolean.TRUE); + } + + if (resolvedPropertySchema instanceof DateTimeSchema) { + propertyExtensions.put(CodegenConstants.IS_DATE_TIME_EXT_NAME, Boolean.TRUE); + } + } } diff --git a/src/main/java/ru/touchin/codegen/TINetworkingCodegenConstants.java b/src/main/java/ru/touchin/codegen/TINetworkingCodegenConstants.java index 55b8c1e..3dadd74 100644 --- a/src/main/java/ru/touchin/codegen/TINetworkingCodegenConstants.java +++ b/src/main/java/ru/touchin/codegen/TINetworkingCodegenConstants.java @@ -4,4 +4,6 @@ public class TINetworkingCodegenConstants { public static final String DATE_FORMAT = "x-custom-date-format"; public static final String DATE_FORMAT_NAME = "x-codegen-date-format-name"; public static final String IS_ISO8601_DATE = "x-codegen-is-iso8601-date"; + public static final String HAS_DATE = "x-has-date"; + public static final String IS_DATE_FORMAT = "x-is-date-format"; } diff --git a/src/main/resources/handlebars/TINetworking/APIDateFormat.mustache b/src/main/resources/handlebars/TINetworking/APIDateFormat.mustache index 990f7d9..1148b14 100644 --- a/src/main/resources/handlebars/TINetworking/APIDateFormat.mustache +++ b/src/main/resources/handlebars/TINetworking/APIDateFormat.mustache @@ -1,36 +1,44 @@ {{>licenseInfo}} + package {{packageName}} import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat /** -* Util object for handling some cases with DateTime e.g. parsing string to DateTime object -*/ + * Util object for handling some cases with DateTime e.g. parsing string to DateTime object + */ object DateFormatUtils { enum class APIDateFormat(val formatValue: String) { - DATE_TIME_FORMAT("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"), - DATE_FORMAT("yyyy-MM-dd"), - TIME_FORMAT("HH:mm:ssZ"), + DATE_TIME_FORMAT("yyyy-MM-dd'T'HH:mm:ss.SSSZZ"), + DATE_FORMAT("yyyy-MM-dd"), + TIME_FORMAT("HH:mm:ssZ"), {{#each apiDateFormats as |value key|}} - {{key}}("{{{value}}}") + {{key}}("{{{value}}}") {{/each}} } /** - * @return the result of parsed string value - * @param value is string value of date time in right format - * @param format is date time format for parsing string value. - * Default value is [Format.DATE_TIME_FORMAT] - * @param defaultValue is value returned in case of exception - */ + * @return the result of parsed string value + * @param value is string value of date time in right format + * @param format is date time format for parsing string value. + * Default value is [Format.DATE_TIME_FORMAT] + * @param defaultValue is value returned in case of exception + */ fun fromString( - value: String, - format: APIDateFormat = APIDateFormat.DATE_TIME_FORMAT, - defaultValue: DateTime? = null - ): DateTime? = runCatching { value.parse(format.formatValue) }.getOrDefault(defaultValue) + value: String?, + format: APIDateFormat = APIDateFormat.DATE_TIME_FORMAT, + defaultValue: DateTime? = null + ): DateTime? = value?.let { + runCatching { value.parse(format.formatValue) }.getOrDefault(defaultValue) + } private fun String.parse(format: String) = DateTimeFormat.forPattern(format).parseDateTime(this) + fun toString( + value: DateTime?, + format: APIDateFormat = APIDateFormat.DATE_TIME_FORMAT + ): String = value?.toString(DateTimeFormat.forPattern(format.formatValue)) ?: "" + } \ No newline at end of file diff --git a/src/main/resources/handlebars/TINetworking/AuthorizationInterceptor.mustache b/src/main/resources/handlebars/TINetworking/AuthorizationInterceptor.mustache new file mode 100644 index 0000000..300789f --- /dev/null +++ b/src/main/resources/handlebars/TINetworking/AuthorizationInterceptor.mustache @@ -0,0 +1,38 @@ +{{>licenseInfo}} + +package {{packageName}} + +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import retrofit2.Invocation +import java.io.IOException + +class AuthorizationInterceptor : Interceptor { + + private var sessionToken: String? = null + + fun setSessionToken(sessionToken: String?) { + this.sessionToken = sessionToken + } + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val request: Request = chain.request() + val requestBuilder: Request.Builder = request.newBuilder() + + if (request.needAuth()) { + {{#authMethods}} + {{#isApiKey}}{{#isKeyInHeader}}requestBuilder.addHeader("{{keyParamName}}", sessionToken){{/isKeyInHeader}}{{/isApiKey}} + {{/authMethods}} + } + + return chain.proceed(requestBuilder.build()) + } +} + +fun Request.needAuth() = tag(Invocation::class.java)?.method()?.getAnnotation(AuthRequest::class.java) != null + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class AuthRequest() diff --git a/src/main/resources/handlebars/TINetworking/EnumJsonAdapters.mustache b/src/main/resources/handlebars/TINetworking/EnumJsonAdapters.mustache deleted file mode 100644 index f7d8e43..0000000 --- a/src/main/resources/handlebars/TINetworking/EnumJsonAdapters.mustache +++ /dev/null @@ -1,13 +0,0 @@ -{{>licenseInfo}} -package {{packageName}} - -import com.squareup.moshi.Moshi -{{#each enumAdaptersImports}} -import {{.}} -{{/each}} - -fun Moshi.Builder.addGeneratedEnumJsonAdapters(): Moshi.Builder = this - .add(GeneratedDateAdapter()) -{{#each enumAdapters}} - .add({{.}}()) -{{/each}} diff --git a/src/main/resources/handlebars/TINetworking/GeneratedDateAdapter.mustache b/src/main/resources/handlebars/TINetworking/GeneratedDateAdapter.mustache deleted file mode 100644 index bd6afe7..0000000 --- a/src/main/resources/handlebars/TINetworking/GeneratedDateAdapter.mustache +++ /dev/null @@ -1,32 +0,0 @@ -{{>licenseInfo}} -package {{packageName}} - -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson -import org.joda.time.DateTime -import {{packageName}}.DateFormatUtils.APIDateFormat - -class GeneratedDateAdapter : JsonAdapter() { - - @FromJson - override fun fromJson(reader: JsonReader): DateTime? { - val dateAsString = reader.nextString() - val date = DateFormatUtils.fromString(value = dateAsString, format = APIDateFormat.DATE_TIME_FORMAT) - ?: DateFormatUtils.fromString(value = dateAsString, format = APIDateFormat.DATE_FORMAT) - ?: DateFormatUtils.fromString(value = dateAsString, format = APIDateFormat.TIME_FORMAT) - {{#each apiDateFormats as |value key|}} - ?: DateFormatUtils.fromString(value = dateAsString, format = APIDateFormat.{{key}}) - {{/each}} - return date - } - - @ToJson - override fun toJson(writer: JsonWriter, value: DateTime?) { - if (value != null) { - writer.value(value.toString()) - } - } -} \ No newline at end of file diff --git a/src/main/resources/handlebars/TINetworking/JsonAdapters.mustache b/src/main/resources/handlebars/TINetworking/JsonAdapters.mustache new file mode 100644 index 0000000..ffa42fa --- /dev/null +++ b/src/main/resources/handlebars/TINetworking/JsonAdapters.mustache @@ -0,0 +1,13 @@ +{{>licenseInfo}} + +package {{packageName}} + +import com.squareup.moshi.Moshi +{{#each jsonAdaptersImports}} +import {{.}} +{{/each}} + +fun Moshi.Builder.addGeneratedJsonAdapters(): Moshi.Builder = this +{{#each jsonAdapters}} + .add({{.}}()) +{{/each}} diff --git a/src/main/resources/handlebars/TINetworking/api.mustache b/src/main/resources/handlebars/TINetworking/api.mustache index f320d58..da00647 100644 --- a/src/main/resources/handlebars/TINetworking/api.mustache +++ b/src/main/resources/handlebars/TINetworking/api.mustache @@ -1,4 +1,5 @@ {{>licenseInfo}} + package {{apiPackage}} import retrofit2.http.DELETE @@ -6,6 +7,7 @@ import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.PUT +import {{packageName}}.AuthRequest {{#imports}}import {{import}} {{/imports}} {{#jodaTime}} @@ -23,10 +25,10 @@ interface {{classname}} { {{/parameters}} * @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} */{{#returnType}} {{/returnType}} + {{#if authMethods}}@AuthRequest{{/if}} @{{httpMethod}}("{{{path}}}") suspend fun {{operationId}}({{#parameters}} - {{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{#hasMore}},{{/hasMore}}{{/parameters}}{{#if authMethods}}{{#if parameters}}, - {{/if}}{{/if}}{{#authMethods}}{{>securityParam}}{{#hasMore}},{{/hasMore}}{{/authMethods}} + {{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{#hasMore}},{{/hasMore}}{{/parameters}} ): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {{/contents}} diff --git a/src/main/resources/handlebars/TINetworking/data_class.mustache b/src/main/resources/handlebars/TINetworking/data_class.mustache index dd34075..4425d71 100644 --- a/src/main/resources/handlebars/TINetworking/data_class.mustache +++ b/src/main/resources/handlebars/TINetworking/data_class.mustache @@ -4,18 +4,56 @@ * @param {{name}} {{{description}}} {{/allVars}} */ +{{#if vendorExtensions.x-has-date}} +{{else}} @JsonClass(generateAdapter = true) +{{/if}} {{#hasVars}}data {{/hasVars}}class {{classname}} ( {{#allVars}} {{#required}}{{>data_class_req_var}}{{^@last}},{{/@last}}{{/required}}{{^required}}{{>data_class_opt_var}}{{^@last}},{{/@last}}{{/required}} {{/allVars}} ) { +{{#if vendorExtensions.x-has-date}} + @JsonClass(generateAdapter = true) + data class {{classname}}Json( + {{#allVars}} + {{#required}}{{>data_class_req_var_adapter}}{{^@last}},{{/@last}}{{/required}}{{^required}}{{>data_class_opt_var_adapter}}{{^@last}},{{/@last}}{{/required}} + {{/allVars}} + ) + + class {{classname}}JsonAdapter() { + @FromJson + fun fromJson(json: {{classname}}Json): {{classname}} { + return {{classname}}( + {{#allVars}} + {{{name}}} = {{#if vendorExtensions.x-is-date-format}}{{packageName}}.DateFormatUtils.fromString( + value = json.{{{name}}}, + format = {{packageName}}.DateFormatUtils.APIDateFormat.{{#if vendorExtensions.x-codegen-is-iso8601-date}}{{#isDate}}DATE_FORMAT{{/isDate}}{{#isDateTime}}DATE_TIME_FORMAT{{/isDateTime}}{{else if vendorExtensions.x-custom-date-format}}{{vendorExtensions.x-codegen-date-format-name}}{{/if}} + ){{#required}} ?: throw com.squareup.moshi.JsonDataException("Non-null value '{{{name}}}' was null at {{classname}}"){{/required}}{{else}}json.{{{name}}}{{/if}}{{^@last}},{{/@last}} + {{/allVars}} + ) + } + + @ToJson + fun toJson(model: {{classname}}): {{classname}}Json { + return {{classname}}Json( + {{#allVars}} + {{{name}}} = {{#if vendorExtensions.x-is-date-format}}{{packageName}}.DateFormatUtils.toString( + value = model.{{{name}}}, + format = {{packageName}}.DateFormatUtils.APIDateFormat.{{#if vendorExtensions.x-codegen-is-iso8601-date}}{{#isDate}}DATE_FORMAT{{/isDate}}{{#isDateTime}}DATE_TIME_FORMAT{{/isDateTime}}{{else if vendorExtensions.x-custom-date-format}}{{vendorExtensions.x-codegen-date-format-name}}{{/if}} + ){{else}}model.{{{name}}}{{/if}}{{^@last}},{{/@last}} + {{/allVars}} + ) + } + } +{{/if}} {{#allVars}} {{#isEnum}} - /** - * {{{description}}} - * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} - */ + + /** + * {{{description}}} + * Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^@last}},{{/@last}}{{/enumVars}}{{/allowableValues}} + */ enum class {{nameInCamelCase}}(val value: {{datatype}}{{#isNullable}}?{{/isNullable}}) { {{#allowableValues}}{{#enumVars}} {{&name}}({{#value}}{{{value}}}{{/value}}{{^value}}null{{/value}}){{^@last}},{{/@last}}{{#@last}};{{/@last}} diff --git a/src/main/resources/handlebars/TINetworking/data_class_opt_var_adapter.mustache b/src/main/resources/handlebars/TINetworking/data_class_opt_var_adapter.mustache new file mode 100644 index 0000000..6f8b7ad --- /dev/null +++ b/src/main/resources/handlebars/TINetworking/data_class_opt_var_adapter.mustache @@ -0,0 +1 @@ + val {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{#if vendorExtensions.x-is-date-format}}kotlin.String{{else}}{{{datatype}}}{{/if}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/src/main/resources/handlebars/TINetworking/data_class_req_var_adapter.mustache b/src/main/resources/handlebars/TINetworking/data_class_req_var_adapter.mustache new file mode 100644 index 0000000..6bcfd59 --- /dev/null +++ b/src/main/resources/handlebars/TINetworking/data_class_req_var_adapter.mustache @@ -0,0 +1 @@ + val {{{name}}}: {{#is this 'enum'}}{{datatypeWithEnum}}{{/is}}{{#isNot this 'enum'}}{{#if vendorExtensions.x-is-date-format}}kotlin.String{{else}}{{{datatype}}}{{/if}}{{/isNot}} \ No newline at end of file diff --git a/src/main/resources/handlebars/TINetworking/model.mustache b/src/main/resources/handlebars/TINetworking/model.mustache index 83cc4c6..38e69f0 100644 --- a/src/main/resources/handlebars/TINetworking/model.mustache +++ b/src/main/resources/handlebars/TINetworking/model.mustache @@ -1,14 +1,10 @@ {{>licenseInfo}} + package {{modelPackage}} import com.squareup.moshi.JsonClass import com.squareup.moshi.ToJson import com.squareup.moshi.FromJson -{{#imports}}import {{import}} -{{/imports}} -{{#jodaTime}} -import org.joda.time.DateTime -{{/jodaTime}} {{#models}} {{#model}} @@ -20,4 +16,4 @@ typealias {{classname}} = {{dataType}} {{#isNot this 'enum'}}{{>data_class}}{{/isNot}} {{/isNot}} {{/model}} -{{/models}} \ No newline at end of file +{{/models}}