Skip to content

Commit 7153a80

Browse files
committed
[scala][http4s] fix escaping of reserved words for correct model deserialization
1 parent 6fdb632 commit 7153a80

File tree

5 files changed

+57
-15
lines changed

5 files changed

+57
-15
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package org.openapitools.codegen.languages;
1818

19+
import com.google.common.collect.ImmutableMap;
20+
import com.samskivert.mustache.Escapers;
21+
import com.samskivert.mustache.Mustache;
1922
import io.swagger.v3.oas.models.media.Schema;
2023
import org.openapitools.codegen.*;
2124
import org.openapitools.codegen.meta.features.*;
2225
import org.openapitools.codegen.model.*;
26+
import org.openapitools.codegen.templating.mustache.EscapeKeywordLambda;
2327
import org.openapitools.codegen.utils.ModelUtils;
2428
import org.slf4j.Logger;
2529
import org.slf4j.LoggerFactory;
@@ -353,8 +357,7 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
353357

354358
if (!cModel.oneOf.isEmpty()) {
355359
cModel.getVendorExtensions().put("x-isSealedTrait", true);
356-
}
357-
else if (cModel.isEnum) {
360+
} else if (cModel.isEnum) {
358361
cModel.getVendorExtensions().put("x-isEnum", true);
359362

360363
} else {
@@ -557,7 +560,34 @@ public String getHelp() {
557560

558561
@Override
559562
public String escapeReservedWord(String name) {
560-
return "_" + name;
563+
if (this.reservedWordsMappings().containsKey(name)) {
564+
return this.reservedWordsMappings().get(name);
565+
}
566+
// Reserved words will be further escaped at the mustache compiler level.
567+
// Scala escaping done here (via `, without compiler escaping) would otherwise be HTML encoded.
568+
return "`" + name + "`";
569+
}
570+
571+
@Override
572+
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
573+
Mustache.Escaper SCALA = new Mustache.Escaper() {
574+
@Override
575+
public String escape(String text) {
576+
// Fix included as suggested by akkie in #6393
577+
// The given text is a reserved word which is escaped by enclosing it with grave accents. If we would
578+
// escape that with the default Mustache `HTML` escaper, then the escaper would also escape our grave
579+
// accents. So we remove the grave accents before the escaping and add it back after the escaping.
580+
if (text.startsWith("`") && text.endsWith("`")) {
581+
String unescaped = text.substring(1, text.length() - 1);
582+
return "`" + Escapers.HTML.escape(unescaped) + "`";
583+
}
584+
585+
// All none reserved words will be escaped with the default Mustache `HTML` escaper
586+
return Escapers.HTML.escape(text);
587+
}
588+
};
589+
590+
return compiler.withEscaper(SCALA);
561591
}
562592

563593
@Override
@@ -807,12 +837,12 @@ private String cpToPathParameter(CodegenParameter cp, Set<String> imports, Map<S
807837

808838
if (_vendorExtensions.size() == 1) { // only `x-type`
809839
if ("String".equals(cp.getDataType())) {
810-
return cp.paramName;
840+
return escapeReservedWordUnapply(cp.baseName);
811841
} else {
812-
return cp.dataType + "Varr(" + cp.paramName + ")";
842+
return cp.dataType + "Varr(" + escapeReservedWordUnapply(cp.baseName) + ")";
813843
}
814844
} else {
815-
return cp.baseName + "Varr(" + cp.paramName + ")";
845+
return cp.baseName + "Varr(" + escapeReservedWordUnapply(cp.baseName) + ")";
816846
}
817847
}
818848

@@ -844,11 +874,23 @@ private String cpToQueryParameter(CodegenParameter cp, Set<String> imports, Map<
844874
}
845875

846876
vendorExtensions.putAll(refineProp(cp, imports));
847-
return cp.baseName + "QueryParam(" + cp.paramName + ")";
877+
return cp.baseName + "QueryParam(" + escapeReservedWordUnapply(cp.baseName) + ")";
848878
}
849879

850880
@Override
851881
public GeneratorLanguage generatorLanguage() {
852882
return GeneratorLanguage.SCALA;
853883
}
884+
885+
@Override
886+
protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
887+
return super.addMustacheLambdas()
888+
.put("escapeReservedWordUnapply", new EscapeKeywordLambda(this::escapeReservedWordUnapply));
889+
}
890+
891+
private String escapeReservedWordUnapply(String value) {
892+
// The unapply method doesn’t allow you to work with reserved variables via backticks;
893+
// in such cases you should use the variable via a placeholder instead.
894+
return isReservedWord(value) ? "_" + value : value;
895+
}
854896
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{^authName}}
2-
delegate.{{operationId}}.handle(req, {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses)
2+
delegate.{{operationId}}.handle(req, {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses)
33
{{/authName}}
44
{{#authName}}
5-
delegate.{{operationId}}.handle_{{authName}}(auth, req, {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses)
5+
delegate.{{operationId}}.handle_{{authName}}(auth, req, {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses)
66
{{/authName}}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{^authName}}
2-
delegate.{{operationId}}.handle(req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses)
2+
delegate.{{operationId}}.handle(req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses)
33
{{/authName}}
44
{{#authName}}
5-
delegate.{{operationId}}.handle_{{authName}}(auth, req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses)
5+
delegate.{{operationId}}.handle_{{authName}}(auth, req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses)
66
{{/authName}}

samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/apis/FakeApi.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ trait FakeApiDelegate[F[_]] {
5757

5858
def handle(
5959
req: Request[F],
60-
_type: String,
61-
_var: Option[String],
60+
`type`: String,
61+
`var`: Option[String],
6262
responses: reservedWordsResponses[F]
6363
): F[Response[F]]
6464

samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/models/types.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import java.time.ZonedDateTime
1515
/**
1616
* Describes the result of uploading an image resource
1717
* @param code
18-
* @param _type
18+
* @param `type`
1919
* @param message
2020
*/
2121

2222
case class ApiResponse(
2323
code: Option[Int],
24-
_type: Option[String],
24+
`type`: Option[String],
2525
message: Option[String]
2626
)
2727
object ApiResponse {

0 commit comments

Comments
 (0)