Skip to content

Commit

Permalink
Merge pull request #1 from stardogventures/typescript-axios-new-version
Browse files Browse the repository at this point in the history
Merging updates to typescript-axios codegen and Kotlin codegen
  • Loading branch information
eonwhite authored Aug 15, 2021
2 parents 6187e9d + 0345f8e commit 14390d2
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";

protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase;
protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.UPPERCASE;

public AbstractKotlinCodegen() {
super();
Expand Down Expand Up @@ -143,14 +143,14 @@ public AbstractKotlinCodegen() {
typeMapping.put("double", "kotlin.Double");
typeMapping.put("number", "java.math.BigDecimal");
typeMapping.put("date-time", "java.time.LocalDateTime");
typeMapping.put("date", "java.time.LocalDateTime");
typeMapping.put("date", "java.time.LocalDate");
typeMapping.put("file", "java.io.File");
typeMapping.put("array", "kotlin.Array");
typeMapping.put("list", "kotlin.Array");
typeMapping.put("array", "kotlin.collections.List");
typeMapping.put("list", "kotlin.collections.List");
typeMapping.put("map", "kotlin.collections.Map");
typeMapping.put("object", "kotlin.Any");
typeMapping.put("binary", "kotlin.Array<kotlin.Byte>");
typeMapping.put("Date", "java.time.LocalDateTime");
typeMapping.put("Date", "java.time.LocalDate");
typeMapping.put("DateTime", "java.time.LocalDateTime");

instantiationTypes.put("array", "arrayOf");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,10 @@ public Map<String, Object> postProcessModels(Map<String, Object> objs) {
Map<String, Object> mo = (Map<String, Object>) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");
cm.imports = new TreeSet(cm.imports);
// name enum with model name, e.g. StatusEnum => Pet.StatusEnum
// name enum with model name, e.g. StatusEnum => PetStatusEnum
for (CodegenProperty var : cm.vars) {
if (Boolean.TRUE.equals(var.isEnum)) {
var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + "." + var.enumName);
var.datatypeWithEnum = var.datatypeWithEnum.replace(var.enumName, cm.classname + var.enumName);
}
}
if (cm.parent != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package io.swagger.codegen.languages;

import io.swagger.codegen.CodegenModel;
import io.swagger.codegen.CodegenOperation;
import io.swagger.codegen.CodegenProperty;
import io.swagger.codegen.SupportingFile;
import io.swagger.models.ModelImpl;

import java.io.File;
import java.util.*;

public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodegen {
public TypeScriptAxiosClientCodegen() {
super();

this.outputFolder = "generated-code/typescript-axios";
embeddedTemplateDir = templateDir = "typescript-axios";

modelTemplateFiles.put("model.mustache", ".ts");
apiTemplateFiles.put("api.mustache", ".ts");

modelPackage = "models";
apiPackage = "resources";
}

@Override
public String getName() {
return "typescript-axios";
}

@Override
public String getHelp() {
return "Generates a TypeScript client library using Axios for HTTP requests.";
}

@Override
public void processOpts() {
super.processOpts();
supportingFiles.add(new SupportingFile("axios.config.mustache", "axios.config.ts"));
}

@Override
public String toModelFilename(String name) {
return toModelName(name);
}

@Override
public String toModelImport(String name) {
return modelPackage() + "/" + toModelFilename(name);
}

@Override
public String toEnumVarName(String name, String datatype) {
if (name.length() == 0) {
return "Empty";
}

// for symbol, e.g. $, #
if (getSymbolName(name) != null) {
return camelize(getSymbolName(name));
}

// number
if ("number".equals(datatype)) {
String varName = "NUMBER_" + name;

varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}

String enumName = name;
if (enumName.matches("\\d.*")) { // starts with number
return "_" + enumName;
} else {
return enumName;
}
}

@Override
public String toEnumName(CodegenProperty property) {
String enumName = toModelName(property.name);

if (enumName.matches("\\d.*")) { // starts with number
return "_" + enumName;
} else {
return enumName;
}
}

@Override
@SuppressWarnings("unchecked")
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
Map<String, Object> result = super.postProcessModels(objs);

// Add additional filename information for imports
List<Map<String, Object>> models = (List<Map<String, Object>>)postProcessModelsEnum(result).get("models");
for (Map<String,Object> mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
mo.put("tsImports", toTsImports(cm.imports));
}

return result;
}

private List<Map<String, String>> toTsImports(Set<String> imports) {
List<Map<String, String>> tsImports = new ArrayList<>();
for(String im : imports) {
HashMap<String, String> tsImport = new HashMap<>();
tsImport.put("classname", im);
tsImport.put("filename", toModelFilename(im));
tsImports.add(tsImport);
}
return tsImports;
}

@Override
public Map<String, Object> postProcessOperations(Map<String, Object> operations) {
// Add additional filename information for model imports in the services
List<Map<String, Object>> imports = (List<Map<String, Object>>) operations.get("imports");
for(Map<String, Object> im : imports) {
im.put("filename", im.get("import"));
im.put("classname", getModelnameFromModelFilename(im.get("filename").toString()));
}

// if there are any form params, we'll need to import stringify
Map<String, Object> opsObj = (Map<String, Object>) operations.get("operations");
List<CodegenOperation> ops = (List<CodegenOperation>)opsObj.get("operation");
boolean anyHasFormParams = false;
for (CodegenOperation op : ops) {
if (op.getHasFormParams()) {
anyHasFormParams = true;
}
}
if (anyHasFormParams) {
operations.put("hasFormParams", true);
}

return operations;
}

private String getModelnameFromModelFilename(String filename) {
return filename.substring((modelPackage() + "/").length());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ io.swagger.codegen.languages.TypeScriptAngularJsClientCodegen
io.swagger.codegen.languages.TypeScriptFetchClientCodegen
io.swagger.codegen.languages.TypeScriptJqueryClientCodegen
io.swagger.codegen.languages.TypeScriptNodeClientCodegen
io.swagger.codegen.languages.TypeScriptAxiosClientCodegen
io.swagger.codegen.languages.UE4CPPGenerator
io.swagger.codegen.languages.UndertowCodegen
io.swagger.codegen.languages.ZendExpressivePathHandlerServerCodegen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.threeten.bp.LocalDateTime
{{/threetenbp}}

{{#operations}}
class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(basePath) {
class {{classname}}(basePath: kotlin.String = "{{{basePath}}}", baseHeaders: Map<String,String> = emptyMap()) : ApiClient(basePath, baseHeaders) {
{{#operation}}
/**
Expand All @@ -20,13 +20,18 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
{{/allParams}}* @return {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}
*/{{#returnType}}
@Suppress("UNCHECKED_CAST"){{/returnType}}
fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
fun {{operationId}}({{#allParams}}{{{paramName}}}: {{{dataType}}}{{^required}}? = null{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to {{paramName}}{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}}
val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{baseName}}" to {{#isContainer}}toMultiValue({{paramName}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{paramName}}"){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}){{/hasQueryParams}}

val localVariableQuery = mutableMapOf<String,List<String>>()
{{#queryParams}}
if ({{baseName}} != null) {
localVariableQuery["{{baseName}}"] = listOf("${{baseName}}")
}
{{/queryParams}}

val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}})
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasProduces}}"Accept" to "{{#produces}}{{#isContainer}}{{mediaType}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{mediaType}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/produces}}"{{/hasProduces}})
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf({{#hasHeaderParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}{{/hasHeaderParams}})
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String?> = mutableMapOf({{#hasHeaderParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}{{/hasHeaderParams}})
localVariableHeaders.putAll(contentHeaders)
localVariableHeaders.putAll(acceptsHeaders)

Expand All @@ -45,6 +50,7 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
ResponseType.Success -> {{#returnType}}(response as Success<*>).data as {{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}}
ResponseType.Informational -> TODO()
ResponseType.Redirection -> TODO()
ResponseType.Retry -> throw RetryException((response as RetryResponse<*>).body as? String ?: "Retry request")
ResponseType.ClientError -> throw ClientException((response as ClientError<*>).body as? String ?: "Client error")
ResponseType.ServerError -> throw ServerException((response as ServerError<*>).message ?: "Server error")
else -> throw kotlin.IllegalStateException("Undefined ResponseType.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ data class {{classname}} (
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{nameInCamelCase}}(val value: {{datatype}}){
enum class {{nameInCamelCase}}(val value: {{{datatype}}}){
{{#allowableValues}}{{#enumVars}}
@Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
@Json(name = {{{value}}}) {{{name}}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}{{/allowableValues}}
}
{{/isEnum}}{{/vars}}{{/hasEnums}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.io.IOException
import java.nio.file.Files;
import java.util.regex.Pattern

open class ApiClient(val baseUrl: String) {
open class ApiClient(val baseUrl: String, val baseHeaders: Map<String, String> = emptyMap()) {
companion object {
protected val ContentType = "Content-Type"
protected val Accept = "Accept"
Expand Down Expand Up @@ -39,15 +39,14 @@ open class ApiClient(val baseUrl: String) {
// content's type *must* be Map<String, Any>
@Suppress("UNCHECKED_CAST")
(content as Map<String,Any>).forEach { key, value ->
(content as Map<String, Any>).forEach { (key, value) ->
if(value::class == File::class) {
val file = value as File
requestBodyBuilder.addFormDataPart(key, file.name, RequestBody.create(MediaType.parse("application/octet-stream"), file))
} else {
val stringValue = value as String
requestBodyBuilder.addFormDataPart(key, stringValue)
}
TODO("Handle other types inside FormDataMediaType")
}
return requestBodyBuilder.build()
Expand Down Expand Up @@ -78,7 +77,7 @@ open class ApiClient(val baseUrl: String) {
contentType = JsonMediaType
}
if(isJsonMime(contentType)){
if (isJsonMime(contentType)) {
return Serializer.moshi.adapter(T::class.java).fromJson(response.body()?.source())
} else if(contentType.equals(String.javaClass)){
return response.body().toString() as T
Expand All @@ -105,7 +104,7 @@ open class ApiClient(val baseUrl: String) {
}
val url = urlBuilder.build()
val headers = defaultHeaders + requestConfig.headers
val headers = defaultHeaders + baseHeaders + requestConfig.headers
if(headers[ContentType] ?: "" == "") {
throw kotlin.IllegalStateException("Missing Content-Type header. This is required.")
Expand Down Expand Up @@ -145,6 +144,11 @@ open class ApiClient(val baseUrl: String) {
response.code(),
response.headers().toMultimap()
)
response.code() == 202 -> return RetryResponse(
responseBody(response, accept),
response.code(),
response.headers().toMultimap()
)
response.isSuccessful -> return Success(
responseBody(response, accept),
response.code(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package {{packageName}}.infrastructure

enum class ResponseType {
Success, Informational, Redirection, ClientError, ServerError
Success, Retry, Informational, Redirection, ClientError, ServerError
}

abstract class ApiInfrastructureResponse<T>(val responseType: ResponseType) {
Expand Down Expand Up @@ -37,4 +37,10 @@ class ServerError<T>(
val body: Any? = null,
override val statusCode: Int = -1,
override val headers: Map<String, List<String>>
): ApiInfrastructureResponse<T>(ResponseType.ServerError)
): ApiInfrastructureResponse<T>(ResponseType.ServerError)

class RetryResponse<T>(
val body: Any? = null,
override val statusCode: Int = -1,
override val headers: Map<String, List<String>> = mapOf()
) : ApiInfrastructureResponse<T>(ResponseType.Retry)
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,23 @@ open class ServerException : RuntimeException {
companion object {
private const val serialVersionUID: Long = 456L
}
}

open class RetryException : RuntimeException {
/**
* Constructs an [RetryException] with no detail message.
*/
constructor() : super()
/**
* Constructs an [RetryException] with the specified detail message.
* @param message the detail message.
*/
constructor(message: kotlin.String) : super(message)
companion object {
private const val serialVersionUID: Long = 789L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ package {{packageName}}.infrastructure
data class RequestConfig(
val method: RequestMethod,
val path: String,
val headers: Map<String, String> = mapOf(),
val headers: Map<String, String?> = mapOf(),
val query: Map<String, List<String>> = mapOf())
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package {{packageName}}.infrastructure

import com.squareup.moshi.KotlinJsonAdapterFactory
import com.squareup.moshi.Moshi
import com.squareup.moshi.Rfc3339DateJsonAdapter
import com.squareup.moshi.*
import java.math.BigDecimal
import java.time.LocalDate
import java.util.*

object Serializer {
@JvmStatic
val moshi: Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(Date::class.java, Rfc3339DateJsonAdapter().nullSafe())
.add(LocalDateAdapter)
.add(BigDecimalAdapter)
.build()
}

object LocalDateAdapter {
@FromJson fun fromJson(string: String) = LocalDate.parse(string)
@ToJson fun toJson(value: LocalDate) = value.toString()
}

object BigDecimalAdapter {
@FromJson fun fromJson(string: String) = BigDecimal(string)
@ToJson fun toJson(value: BigDecimal) = value.toString()
}
Loading

0 comments on commit 14390d2

Please sign in to comment.