Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[haskell-http-client] bug fixes; path & newtype generation issues #6638

Merged
merged 1 commit into from
Oct 10, 2017
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 @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Thanks for pointing it out. I wasn't aware JAXRS spec generator is using a different approach.

We'll accept both approach.


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