Skip to content

Commit 8424845

Browse files
jonschoningfvarose
authored andcommitted
[haskell-http-client] bug fixes; path & newtype generation issues (swagger-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
1 parent e91fdf5 commit 8424845

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+4093
-4127
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellHttpClientCodegen.java

Lines changed: 143 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@
2626

2727
import org.apache.commons.lang3.StringUtils;
2828
import org.apache.commons.lang3.StringEscapeUtils;
29-
import org.apache.commons.lang3.text.WordUtils;
3029

3130
import java.util.regex.Matcher;
3231

3332
public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenConfig {
3433

3534
// source folder where to write the files
3635
protected String sourceFolder = "src";
37-
protected String apiVersion = "0.0.1";
3836

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

6866
protected Map<String, CodegenParameter> uniqueParamsByName = new HashMap<String, CodegenParameter>();
6967
protected Set<String> typeNames = new HashSet<String>();
70-
protected Map<String, Map<String,String>> allMimeTypes = new HashMap<String, Map<String,String>>();
7168
protected Map<String, String> knownMimeDataTypes = new HashMap<String, String>();
7269
protected Map<String, Set<String>> modelMimeTypes = new HashMap<String, Set<String>>();
7370
protected String lastTag = "";
@@ -124,7 +121,6 @@ public HaskellHttpClientCodegen() {
124121
)
125122
);
126123

127-
additionalProperties.put("apiVersion", apiVersion);
128124
additionalProperties.put("artifactId", artifactId);
129125
additionalProperties.put("artifactVersion", artifactVersion);
130126

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

401-
//copy input swagger to output folder
402-
try {
403-
String swaggerJson = Json.pretty(swagger);
404-
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson);
405-
} catch (IOException e) {
406-
throw new RuntimeException(e.getMessage(), e.getCause());
407-
}
397+
WriteInputSwaggerToFile(swagger);
408398

409399
super.preprocessSwagger(swagger);
410400
}
@@ -461,7 +451,6 @@ public String toInstantiationType(Property p) {
461451
return null;
462452
}
463453
}
464-
465454
@Override
466455
public CodegenOperation fromOperation(String resourcePath, String httpMethod, Operation operation, Map<String, Model> definitions, Swagger swagger) {
467456
CodegenOperation op = super.fromOperation(resourcePath, httpMethod, operation, definitions, swagger);
@@ -486,100 +475,20 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op
486475
if(!param.required) {
487476
op.vendorExtensions.put("x-hasOptionalParams", true);
488477
}
489-
if (typeMapping.containsKey(param.dataType) || param.isPrimitiveType || param.isListContainer || param.isMapContainer || param.isFile) {
490-
String paramNameType = toTypeName("Param", param.paramName);
491-
492-
if (uniqueParamsByName.containsKey(paramNameType)) {
493-
CodegenParameter lastParam = this.uniqueParamsByName.get(paramNameType);
494-
if (lastParam.dataType != null && lastParam.dataType.equals(param.dataType)) {
495-
param.vendorExtensions.put("x-duplicate", true);
496-
} else {
497-
paramNameType = paramNameType + param.dataType;
498-
while (typeNames.contains(paramNameType)) {
499-
paramNameType = generateNextName(paramNameType);
500-
}
501-
uniqueParamsByName.put(paramNameType, param);
502-
}
503-
} else {
504-
while (typeNames.contains(paramNameType)) {
505-
paramNameType = generateNextName(paramNameType);
506-
}
507-
uniqueParamsByName.put(paramNameType, param);
508-
}
509478

510-
param.vendorExtensions.put("x-paramNameType", paramNameType);
511-
typeNames.add(paramNameType);
512-
}
513-
}
514-
if (op.getHasPathParams()) {
515-
String remainingPath = op.path;
516-
for (CodegenParameter param : op.pathParams) {
517-
String[] pieces = remainingPath.split("\\{" + param.baseName + "\\}");
518-
if (pieces.length == 0)
519-
throw new RuntimeException("paramName {" + param.baseName + "} not in path " + op.path);
520-
if (pieces.length > 2)
521-
throw new RuntimeException("paramName {" + param.baseName + "} found multiple times in path " + op.path);
522-
if (pieces.length == 2) {
523-
param.vendorExtensions.put("x-pathPrefix", pieces[0]);
524-
remainingPath = pieces[1];
525-
} else {
526-
if (remainingPath.startsWith("{" + param.baseName + "}")) {
527-
remainingPath = pieces[0];
528-
} else {
529-
param.vendorExtensions.put("x-pathPrefix", pieces[0]);
530-
remainingPath = "";
531-
}
532-
}
533-
}
534-
op.vendorExtensions.put("x-hasPathParams", true);
535-
if (remainingPath.length() > 0) {
536-
op.vendorExtensions.put("x-pathSuffix", remainingPath);
537-
}
538-
} else {
539-
op.vendorExtensions.put("x-hasPathParams", false);
540-
op.vendorExtensions.put("x-pathSuffix", op.path);
541-
}
542-
for (CodegenParameter param : op.queryParams) {
543-
}
544-
for (CodegenParameter param : op.headerParams) {
545-
}
546-
for (CodegenParameter param : op.bodyParams) {
547-
}
548-
for (CodegenParameter param : op.formParams) {
479+
deduplicateParameter(param);
549480
}
550481

551-
if (op.hasConsumes) {
552-
for (Map<String, String> m : op.consumes) {
553-
processMediaType(op,m);
554-
}
555-
if (isMultipartOperation(op.consumes)) {
556-
op.isMultipart = Boolean.TRUE;
557-
}
558-
}
559-
if (op.hasProduces) {
560-
for (Map<String, String> m : op.produces) {
561-
processMediaType(op,m);
562-
}
563-
}
482+
processPathExpr(op);
564483

565-
String returnType = op.returnType;
566-
if (returnType == null || returnType.equals("null")) {
567-
if(op.hasProduces) {
568-
returnType = "res";
569-
op.vendorExtensions.put("x-hasUnknownReturn", true);
570-
} else {
571-
returnType = "NoContent";
572-
}
573-
}
574-
if (returnType.indexOf(" ") >= 0) {
575-
returnType = "(" + returnType + ")";
576-
}
577-
op.vendorExtensions.put("x-returnType", returnType);
484+
processProducesConsumes(op);
578485

486+
processReturnType(op);
579487

580488
return op;
581489
}
582-
490+
491+
@Override
583492
public List<CodegenSecurity> fromSecurity(Map<String, SecuritySchemeDefinition> schemes) {
584493
List<CodegenSecurity> secs = super.fromSecurity(schemes);
585494
for(CodegenSecurity sec : secs) {
@@ -603,8 +512,26 @@ public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
603512
}
604513

605514
additionalProperties.put("x-hasUnknownMimeTypes", !unknownMimeTypes.isEmpty());
515+
516+
Collections.sort(unknownMimeTypes, new Comparator<Map<String, String>>() {
517+
@Override
518+
public int compare(Map<String, String> o1, Map<String, String> o2) {
519+
return o1.get(MEDIA_TYPE).compareTo(o2.get(MEDIA_TYPE));
520+
}
521+
});
606522
additionalProperties.put("x-unknownMimeTypes", unknownMimeTypes);
607-
additionalProperties.put("x-allUniqueParams", uniqueParamsByName.values());
523+
524+
ArrayList<CodegenParameter> params = new ArrayList<>(uniqueParamsByName.values());
525+
Collections.sort(params, new Comparator<CodegenParameter>() {
526+
@Override
527+
public int compare(CodegenParameter o1, CodegenParameter o2) {
528+
return
529+
((String) o1.vendorExtensions.get("x-paramNameType"))
530+
.compareTo(
531+
(String) o2.vendorExtensions.get("x-paramNameType"));
532+
}
533+
});
534+
additionalProperties.put("x-allUniqueParams", params);
608535

609536
return ret;
610537
}
@@ -687,10 +614,114 @@ public boolean isDataTypeBinary(final String dataType) {
687614
return dataType != null && dataType.equals("B.ByteString");
688615
}
689616

617+
//copy input swagger to output folder
618+
private void WriteInputSwaggerToFile(Swagger swagger) {
619+
try {
620+
String swaggerJson = Json.pretty(swagger);
621+
FileUtils.writeStringToFile(new File(outputFolder + File.separator + "swagger.json"), swaggerJson);
622+
} catch (IOException e) {
623+
throw new RuntimeException(e.getMessage(), e.getCause());
624+
}
625+
}
626+
627+
private void processReturnType(CodegenOperation op) {
628+
String returnType = op.returnType;
629+
if (returnType == null || returnType.equals("null")) {
630+
if(op.hasProduces) {
631+
returnType = "res";
632+
op.vendorExtensions.put("x-hasUnknownReturn", true);
633+
} else {
634+
returnType = "NoContent";
635+
}
636+
}
637+
if (returnType.indexOf(" ") >= 0) {
638+
returnType = "(" + returnType + ")";
639+
}
640+
op.vendorExtensions.put("x-returnType", returnType);
641+
}
642+
643+
private void processProducesConsumes(CodegenOperation op) {
644+
if (op.hasConsumes) {
645+
for (Map<String, String> m : op.consumes) {
646+
processMediaType(op,m);
647+
}
648+
if (isMultipartOperation(op.consumes)) {
649+
op.isMultipart = Boolean.TRUE;
650+
}
651+
}
652+
if (op.hasProduces) {
653+
for (Map<String, String> m : op.produces) {
654+
processMediaType(op,m);
655+
}
656+
}
657+
}
658+
659+
private void deduplicateParameter(CodegenParameter param) {
660+
if (typeMapping.containsKey(param.dataType) || param.isPrimitiveType || param.isListContainer || param.isMapContainer || param.isFile) {
661+
662+
String paramNameType = toTypeName("Param", param.paramName);
663+
664+
if (uniqueParamsByName.containsKey(paramNameType)) {
665+
if(!checkParamForDuplicates(paramNameType, param)) {
666+
paramNameType = paramNameType + param.dataType;
667+
if(!checkParamForDuplicates(paramNameType, param)) {
668+
while (typeNames.contains(paramNameType)) {
669+
paramNameType = generateNextName(paramNameType);
670+
if(checkParamForDuplicates(paramNameType, param)) {
671+
break;
672+
}
673+
}
674+
}
675+
676+
uniqueParamsByName.put(paramNameType, param);
677+
}
678+
} else {
679+
680+
while (typeNames.contains(paramNameType)) {
681+
paramNameType = generateNextName(paramNameType);
682+
if(checkParamForDuplicates(paramNameType, param)) {
683+
break;
684+
}
685+
}
686+
687+
uniqueParamsByName.put(paramNameType, param);
688+
}
689+
690+
param.vendorExtensions.put("x-paramNameType", paramNameType);
691+
typeNames.add(paramNameType);
692+
}
693+
}
694+
695+
public Boolean checkParamForDuplicates(String paramNameType, CodegenParameter param) {
696+
CodegenParameter lastParam = this.uniqueParamsByName.get(paramNameType);
697+
if (lastParam != null && lastParam.dataType != null && lastParam.dataType.equals(param.dataType)) {
698+
param.vendorExtensions.put("x-duplicate", true);
699+
return true;
700+
}
701+
return false;
702+
}
703+
704+
// build the parameterized path segments, according to pathParams
705+
private void processPathExpr(CodegenOperation op) {
706+
String xPath = "[\"" + escapeText(op.path) + "\"]";
707+
if (op.getHasPathParams()) {
708+
for (CodegenParameter param : op.pathParams) {
709+
xPath = xPath.replaceAll("\\{" + param.baseName + "\\}", "\",toPath " + param.paramName + ",\"");
710+
}
711+
xPath = xPath.replaceAll(",\"\",", ",");
712+
xPath = xPath.replaceAll("\"\",", ",");
713+
xPath = xPath.replaceAll(",\"\"", ",");
714+
xPath = xPath.replaceAll("^\\[,", "[");
715+
xPath = xPath.replaceAll(",\\]$", "]");
716+
}
717+
op.vendorExtensions.put("x-path", xPath);
718+
}
719+
720+
690721
private void processMediaType(CodegenOperation op, Map<String, String> m) {
691722
String mediaType = m.get(MEDIA_TYPE);
692723

693-
if(StringUtils.isBlank(mediaType)) return;
724+
if (StringUtils.isBlank(mediaType)) return;
694725

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

702-
allMimeTypes.put(mediaType, m);
703-
if(!knownMimeDataTypes.containsKey(mediaType) && !unknownMimeTypes.contains(m)) {
733+
if (!knownMimeDataTypes.containsValue(mimeType) && !unknownMimeTypesContainsType(mimeType)) {
704734
unknownMimeTypes.add(m);
705735
}
706736
for (CodegenParameter param : op.allParams) {
@@ -712,6 +742,17 @@ private void processMediaType(CodegenOperation op, Map<String, String> m) {
712742
}
713743
}
714744

745+
private Boolean unknownMimeTypesContainsType(String mimeType) {
746+
for(Map<String,String> m : unknownMimeTypes) {
747+
String mimeType0 = m.get(MEDIA_DATA_TYPE);
748+
if(mimeType0 != null && mimeType0.equals(mimeType)) {
749+
return true;
750+
}
751+
}
752+
753+
return false;
754+
}
755+
715756
public String firstLetterToUpper(String word) {
716757
if (word.length() == 0) {
717758
return word;

modules/swagger-codegen/src/main/resources/haskell-http-client/API.mustache

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,14 @@ import qualified Prelude as P
8888
-> {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#vendorExtensions.x-paramNameType}}{{{.}}}{{/vendorExtensions.x-paramNameType}}{{^vendorExtensions.x-paramNameType}}{{{dataType}}}{{/vendorExtensions.x-paramNameType}} -- ^ "{{{paramName}}}"{{#description}} - {{/description}} {{{description}}}
8989
-> {{/required}}{{/allParams}}{{requestType}} {{{vendorExtensions.x-operationType}}} {{#vendorExtensions.x-hasBodyOrFormParam}}contentType{{/vendorExtensions.x-hasBodyOrFormParam}}{{^vendorExtensions.x-hasBodyOrFormParam}}MimeNoContent{{/vendorExtensions.x-hasBodyOrFormParam}} {{vendorExtensions.x-returnType}}
9090
{{operationId}} {{#vendorExtensions.x-hasBodyOrFormParam}}_ {{/vendorExtensions.x-hasBodyOrFormParam}}{{#allParams}}{{#required}}{{#isBodyParam}}{{{paramName}}}{{/isBodyParam}}{{^isBodyParam}}({{{vendorExtensions.x-paramNameType}}} {{{paramName}}}){{/isBodyParam}} {{/required}}{{/allParams}}=
91-
_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}}
92-
{{#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}}
93-
`_hasAuthType` (P.Proxy :: P.Proxy {{name}}){{/authMethods}}{{#isDeprecated}}
91+
_mkRequest "{{httpMethod}}" {{{vendorExtensions.x-path}}}{{#authMethods}}
92+
`_hasAuthType` (P.Proxy :: P.Proxy {{name}}){{/authMethods}}{{#allParams}}{{#required}}{{#isHeaderParam}}
93+
`setHeader` {{>_headerColl}} ("{{{baseName}}}", {{{paramName}}}){{/isHeaderParam}}{{#isQueryParam}}
94+
`setQuery` {{>_queryColl}} ("{{{baseName}}}", Just {{{paramName}}}){{/isQueryParam}}{{#isFormParam}}{{#isFile}}
95+
`_addMultiFormPart` NH.partFileSource "{{{baseName}}}" {{{paramName}}}{{/isFile}}{{^isFile}}{{#isMultipart}}
96+
`_addMultiFormPart` NH.partLBS "{{{baseName}}}" (mimeRender' MimeMultipartFormData {{{paramName}}}){{/isMultipart}}{{^isMultipart}}
97+
`addForm` {{>_formColl}} ("{{{baseName}}}", {{{paramName}}}){{/isMultipart}}{{/isFile}}{{/isFormParam}}{{#isBodyParam}}
98+
`setBodyParam` {{{paramName}}}{{/isBodyParam}}{{/required}}{{/allParams}}{{#isDeprecated}}
9499

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

@@ -138,13 +143,6 @@ class HasOptionalParam req param where
138143

139144
infixl 2 -&-
140145

141-
-- * Request Parameter Types
142-
143-
{{#x-allUniqueParams}}
144-
-- | {{{vendorExtensions.x-paramNameType}}}
145-
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
146-
{{/x-allUniqueParams}}
147-
148146
-- * {{requestType}}
149147

150148
-- | Represents a request. The "req" type variable is the request type. The "res" type variable is the response type.

modules/swagger-codegen/src/main/resources/haskell-http-client/Model.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ mk{{classname}} {{#requiredVars}}{{name}} {{/requiredVars}}=
113113
{{/model}}
114114
{{/models}}
115115

116+
-- * Parameter newtypes
117+
118+
{{#x-allUniqueParams}}
119+
newtype {{{vendorExtensions.x-paramNameType}}} = {{{vendorExtensions.x-paramNameType}}} { un{{{vendorExtensions.x-paramNameType}}} :: {{{dataType}}} } deriving (P.Eq, P.Show{{#isBodyParam}}, A.ToJSON{{/isBodyParam}})
120+
{{/x-allUniqueParams}}
121+
116122
-- * Utils
117123

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

0 commit comments

Comments
 (0)