Skip to content

Commit

Permalink
[haskell-http-client] bug fixes; path & newtype generation issues (sw…
Browse files Browse the repository at this point in the history
…agger-api#6638)

* fix path generation/param-substitution issues

* fix newtype de-duplication issues

* refactoring only

* correct version in comments

* prevent duplicate MimeTypes

* sort parameter newtypes
  • Loading branch information
jonschoning authored and fvarose committed Oct 12, 2017
1 parent e91fdf5 commit 8424845
Show file tree
Hide file tree
Showing 43 changed files with 4,093 additions and 4,127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.text.WordUtils;

import java.util.regex.Matcher;

public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenConfig {

// source folder where to write the files
protected String sourceFolder = "src";
protected String apiVersion = "0.0.1";

protected String artifactId = "swagger-haskell-http-client";
protected String artifactVersion = "1.0.0";
Expand Down Expand Up @@ -67,7 +65,6 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC

protected Map<String, CodegenParameter> uniqueParamsByName = new HashMap<String, CodegenParameter>();
protected Set<String> typeNames = new HashSet<String>();
protected Map<String, Map<String,String>> allMimeTypes = new HashMap<String, Map<String,String>>();
protected Map<String, String> knownMimeDataTypes = new HashMap<String, String>();
protected Map<String, Set<String>> modelMimeTypes = new HashMap<String, Set<String>>();
protected String lastTag = "";
Expand Down Expand Up @@ -124,7 +121,6 @@ public HaskellHttpClientCodegen() {
)
);

additionalProperties.put("apiVersion", apiVersion);
additionalProperties.put("artifactId", artifactId);
additionalProperties.put("artifactVersion", artifactVersion);

Expand Down Expand Up @@ -398,13 +394,7 @@ public void preprocessSwagger(Swagger swagger) {
additionalProperties.put("configType", apiName + "Config");
additionalProperties.put("swaggerVersion", swagger.getSwagger());

//copy input swagger to output folder
try {
String swaggerJson = Json.pretty(swagger);
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e.getCause());
}
WriteInputSwaggerToFile(swagger);

super.preprocessSwagger(swagger);
}
Expand Down Expand Up @@ -461,7 +451,6 @@ public String toInstantiationType(Property p) {
return null;
}
}

@Override
public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map<String, Model> definitions, Swagger swagger) {
CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger);
Expand All @@ -486,100 +475,20 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op
if(!param.required) {
op.vendorExtensions.put("x-hasOptionalParams", true);
}
if (typeMapping.containsKey(param.dataType) || param.isPrimitiveType || param.isListContainer || param.isMapContainer || param.isFile) {
String paramNameType = toTypeName("Param", param.paramName);

if (uniqueParamsByName.containsKey(paramNameType)) {
CodegenParameter lastParam = this.uniqueParamsByName.get(paramNameType);
if (lastParam.dataType != null && lastParam.dataType.equals(param.dataType)) {
param.vendorExtensions.put("x-duplicate", true);
} else {
paramNameType = paramNameType + param.dataType;
while (typeNames.contains(paramNameType)) {
paramNameType = generateNextName(paramNameType);
}
uniqueParamsByName.put(paramNameType, param);
}
} else {
while (typeNames.contains(paramNameType)) {
paramNameType = generateNextName(paramNameType);
}
uniqueParamsByName.put(paramNameType, param);
}

param.vendorExtensions.put("x-paramNameType", paramNameType);
typeNames.add(paramNameType);
}
}
if (op.getHasPathParams()) {
String remainingPath = op.path;
for (CodegenParameter param : op.pathParams) {
String[] pieces = remainingPath.split("\\{" + param.baseName + "\\}");
if (pieces.length == 0)
throw new RuntimeException("paramName {" + param.baseName + "} not in path " + op.path);
if (pieces.length > 2)
throw new RuntimeException("paramName {" + param.baseName + "} found multiple times in path " + op.path);
if (pieces.length == 2) {
param.vendorExtensions.put("x-pathPrefix", pieces[0]);
remainingPath = pieces[1];
} else {
if (remainingPath.startsWith("{" + param.baseName + "}")) {
remainingPath = pieces[0];
} else {
param.vendorExtensions.put("x-pathPrefix", pieces[0]);
remainingPath = "";
}
}
}
op.vendorExtensions.put("x-hasPathParams", true);
if (remainingPath.length() > 0) {
op.vendorExtensions.put("x-pathSuffix", remainingPath);
}
} else {
op.vendorExtensions.put("x-hasPathParams", false);
op.vendorExtensions.put("x-pathSuffix", op.path);
}
for (CodegenParameter param : op.queryParams) {
}
for (CodegenParameter param : op.headerParams) {
}
for (CodegenParameter param : op.bodyParams) {
}
for (CodegenParameter param : op.formParams) {
deduplicateParameter(param);
}

if (op.hasConsumes) {
for (Map<String, String> m : op.consumes) {
processMediaType(op,m);
}
if (isMultipartOperation(op.consumes)) {
op.isMultipart = Boolean.TRUE;
}
}
if (op.hasProduces) {
for (Map<String, String> m : op.produces) {
processMediaType(op,m);
}
}
processPathExpr(op);

String returnType = op.returnType;
if (returnType == null || returnType.equals("null")) {
if(op.hasProduces) {
returnType = "res";
op.vendorExtensions.put("x-hasUnknownReturn", true);
} else {
returnType = "NoContent";
}
}
if (returnType.indexOf(" ") >= 0) {
returnType = "(" + returnType + ")";
}
op.vendorExtensions.put("x-returnType", returnType);
processProducesConsumes(op);

processReturnType(op);

return op;
}


@Override
public List<CodegenSecurity> fromSecurity(Map<String, SecuritySchemeDefinition> schemes) {
List<CodegenSecurity> secs = super.fromSecurity(schemes);
for(CodegenSecurity sec : secs) {
Expand All @@ -603,8 +512,26 @@ public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
}

additionalProperties.put("x-hasUnknownMimeTypes", !unknownMimeTypes.isEmpty());

Collections.sort(unknownMimeTypes, new Comparator<Map<String, String>>() {
@Override
public int compare(Map<String, String> o1, Map<String, String> o2) {
return o1.get(MEDIA_TYPE).compareTo(o2.get(MEDIA_TYPE));
}
});
additionalProperties.put("x-unknownMimeTypes", unknownMimeTypes);
additionalProperties.put("x-allUniqueParams", uniqueParamsByName.values());

ArrayList<CodegenParameter> params = new ArrayList<>(uniqueParamsByName.values());
Collections.sort(params, new Comparator<CodegenParameter>() {
@Override
public int compare(CodegenParameter o1, CodegenParameter o2) {
return
((String) o1.vendorExtensions.get("x-paramNameType"))
.compareTo(
(String) o2.vendorExtensions.get("x-paramNameType"));
}
});
additionalProperties.put("x-allUniqueParams", params);

return ret;
}
Expand Down Expand Up @@ -687,10 +614,114 @@ public boolean isDataTypeBinary(final String dataType) {
return dataType != null && dataType.equals("B.ByteString");
}

//copy input swagger to output folder
private void WriteInputSwaggerToFile(Swagger swagger) {
try {
String swaggerJson = Json.pretty(swagger);
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson);
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e.getCause());
}
}

private void processReturnType(CodegenOperation op) {
String returnType = op.returnType;
if (returnType == null || returnType.equals("null")) {
if(op.hasProduces) {
returnType = "res";
op.vendorExtensions.put("x-hasUnknownReturn", true);
} else {
returnType = "NoContent";
}
}
if (returnType.indexOf(" ") >= 0) {
returnType = "(" + returnType + ")";
}
op.vendorExtensions.put("x-returnType", returnType);
}

private void processProducesConsumes(CodegenOperation op) {
if (op.hasConsumes) {
for (Map<String, String> m : op.consumes) {
processMediaType(op,m);
}
if (isMultipartOperation(op.consumes)) {
op.isMultipart = Boolean.TRUE;
}
}
if (op.hasProduces) {
for (Map<String, String> m : op.produces) {
processMediaType(op,m);
}
}
}

private void deduplicateParameter(CodegenParameter param) {
if (typeMapping.containsKey(param.dataType) || param.isPrimitiveType || param.isListContainer || param.isMapContainer || param.isFile) {

String paramNameType = toTypeName("Param", param.paramName);

if (uniqueParamsByName.containsKey(paramNameType)) {
if(!checkParamForDuplicates(paramNameType, param)) {
paramNameType = paramNameType + param.dataType;
if(!checkParamForDuplicates(paramNameType, param)) {
while (typeNames.contains(paramNameType)) {
paramNameType = generateNextName(paramNameType);
if(checkParamForDuplicates(paramNameType, param)) {
break;
}
}
}

uniqueParamsByName.put(paramNameType, param);
}
} else {

while (typeNames.contains(paramNameType)) {
paramNameType = generateNextName(paramNameType);
if(checkParamForDuplicates(paramNameType, param)) {
break;
}
}

uniqueParamsByName.put(paramNameType, param);
}

param.vendorExtensions.put("x-paramNameType", paramNameType);
typeNames.add(paramNameType);
}
}

public Boolean checkParamForDuplicates(String paramNameType, CodegenParameter param) {
CodegenParameter lastParam = this.uniqueParamsByName.get(paramNameType);
if (lastParam != null && lastParam.dataType != null && lastParam.dataType.equals(param.dataType)) {
param.vendorExtensions.put("x-duplicate", true);
return true;
}
return false;
}

// build the parameterized path segments, according to pathParams
private void processPathExpr(CodegenOperation op) {
String xPath = "[\"" + escapeText(op.path) + "\"]";
if (op.getHasPathParams()) {
for (CodegenParameter param : op.pathParams) {
xPath = xPath.replaceAll("\\{" + param.baseName + "\\}", "\",toPath " + param.paramName + ",\"");
}
xPath = xPath.replaceAll(",\"\",", ",");
xPath = xPath.replaceAll("\"\",", ",");
xPath = xPath.replaceAll(",\"\"", ",");
xPath = xPath.replaceAll("^\\[,", "[");
xPath = xPath.replaceAll(",\\]$", "]");
}
op.vendorExtensions.put("x-path", xPath);
}


private void processMediaType(CodegenOperation op, Map<String, String> m) {
String mediaType = m.get(MEDIA_TYPE);

if(StringUtils.isBlank(mediaType)) return;
if (StringUtils.isBlank(mediaType)) return;

String mimeType = getMimeDataType(mediaType);
typeNames.add(mimeType);
Expand All @@ -699,8 +730,7 @@ private void processMediaType(CodegenOperation op, Map<String, String> m) {
m.put(MEDIA_IS_JSON, "true");
}

allMimeTypes.put(mediaType, m);
if(!knownMimeDataTypes.containsKey(mediaType) && !unknownMimeTypes.contains(m)) {
if (!knownMimeDataTypes.containsValue(mimeType) && !unknownMimeTypesContainsType(mimeType)) {
unknownMimeTypes.add(m);
}
for (CodegenParameter param : op.allParams) {
Expand All @@ -712,6 +742,17 @@ private void processMediaType(CodegenOperation op, Map<String, String> m) {
}
}

private Boolean unknownMimeTypesContainsType(String mimeType) {
for(Map<String,String> m : unknownMimeTypes) {
String mimeType0 = m.get(MEDIA_DATA_TYPE);
if(mimeType0 != null && mimeType0.equals(mimeType)) {
return true;
}
}

return false;
}

public String firstLetterToUpper(String word) {
if (word.length() == 0) {
return word;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ import qualified Prelude as P
-> {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}} -- ^ "{{{paramName}}}"{{#description}} - {{/description}} {{{description}}}
-> {{/required}}{{/allParams}}{{requestType}} {{{vendorExtensions.x-operationType}}} {{#vendorExtensions.x-hasBodyOrFormParam}}contentType{{/vendorExtensions.x-hasBodyOrFormParam}}{{^vendorExtensions.x-hasBodyOrFormParam}}MimeNoContent{{/vendorExtensions.x-hasBodyOrFormParam}} {{vendorExtensions.x-returnType}}
{{operationId}} {{#vendorExtensions.x-hasBodyOrFormParam}}_ {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#isBodyParam}}{{{paramName}}}{{/isBodyParam}}{{^isBodyParam}}({{{vendorExtensions.x-paramNameType}}} {{{paramName}}}){{/isBodyParam}} {{/required}}{{/allParams}}=
_mkRequest "{{httpMethod}}" [{{#pathParams}}{{#vendorExtensions.x-pathPrefix}}"{{.}}",{{/vendorExtensions.x-pathPrefix}}toPath {{{paramName}}}{{#hasMore}},{{/hasMore}}{{/pathParams}}{{#vendorExtensions.x-pathSuffix}}{{#vendorExtensions.x-hasPathParams}},{{/vendorExtensions.x-hasPathParams}}"{{.}}"{{/vendorExtensions.x-pathSuffix}}]{{#allParams}}{{#required}}
{{#isHeaderParam}}`setHeader` {{>_headerColl}} ("{{{baseName}}}", {{{paramName}}}){{/isHeaderParam}}{{#isQueryParam}}`setQuery` {{>_queryColl}} ("{{{baseName}}}", Just {{{paramName}}}){{/isQueryParam}}{{#isFormParam}}{{#isFile}}`_addMultiFormPart` NH.partFileSource "{{{baseName}}}" {{{paramName}}}{{/isFile}}{{^isFile}}{{#isMultipart}}`_addMultiFormPart` NH.partLBS "{{{baseName}}}" (mimeRender' MimeMultipartFormData {{{paramName}}}){{/isMultipart}}{{^isMultipart}}`addForm` {{>_formColl}} ("{{{baseName}}}", {{{paramName}}}){{/isMultipart}}{{/isFile}}{{/isFormParam}}{{#isBodyParam}}`setBodyParam` {{{paramName}}}{{/isBodyParam}}{{/required}}{{/allParams}}{{#authMethods}}
`_hasAuthType` (P.Proxy :: P.Proxy {{name}}){{/authMethods}}{{#isDeprecated}}
_mkRequest "{{httpMethod}}" {{{vendorExtensions.x-path}}}{{#authMethods}}
`_hasAuthType` (P.Proxy :: P.Proxy {{name}}){{/authMethods}}{{#allParams}}{{#required}}{{#isHeaderParam}}
`setHeader` {{>_headerColl}} ("{{{baseName}}}", {{{paramName}}}){{/isHeaderParam}}{{#isQueryParam}}
`setQuery` {{>_queryColl}} ("{{{baseName}}}", Just {{{paramName}}}){{/isQueryParam}}{{#isFormParam}}{{#isFile}}
`_addMultiFormPart` NH.partFileSource "{{{baseName}}}" {{{paramName}}}{{/isFile}}{{^isFile}}{{#isMultipart}}
`_addMultiFormPart` NH.partLBS "{{{baseName}}}" (mimeRender' MimeMultipartFormData {{{paramName}}}){{/isMultipart}}{{^isMultipart}}
`addForm` {{>_formColl}} ("{{{baseName}}}", {{{paramName}}}){{/isMultipart}}{{/isFile}}{{/isFormParam}}{{#isBodyParam}}
`setBodyParam` {{{paramName}}}{{/isBodyParam}}{{/required}}{{/allParams}}{{#isDeprecated}}

{-# DEPRECATED {{operationId}} "" #-}{{/isDeprecated}}

Expand Down Expand Up @@ -138,13 +143,6 @@ class HasOptionalParam req param where

infixl 2 -&-

-- * Request Parameter Types

{{#x-allUniqueParams}}
-- | {{{vendorExtensions.x-paramNameType}}}
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
{{/x-allUniqueParams}}

-- * {{requestType}}

-- | Represents a request. The "req" type variable is the request type. The "res" type variable is the response type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ mk{{classname}} {{#requiredVars}}{{name}} {{/requiredVars}}=
{{/model}}
{{/models}}

-- * Parameter newtypes

{{#x-allUniqueParams}}
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
{{/x-allUniqueParams}}

-- * Utils

-- | Removes Null fields. (OpenAPI-Specification 2.0 does not allow Null in JSON)
Expand Down
Loading

0 comments on commit 8424845

Please sign in to comment.