Skip to content

Commit 738f4d0

Browse files
committed
[scala][http4s] fix codegen for using reserved words in openapi
1 parent 6fdb632 commit 738f4d0

File tree

5 files changed

+56
-13
lines changed

5 files changed

+56
-13
lines changed

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

Lines changed: 48 additions & 5 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;
@@ -557,7 +561,34 @@ public String getHelp() {
557561

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

563594
@Override
@@ -807,12 +838,12 @@ private String cpToPathParameter(CodegenParameter cp, Set<String> imports, Map<S
807838

808839
if (_vendorExtensions.size() == 1) { // only `x-type`
809840
if ("String".equals(cp.getDataType())) {
810-
return cp.paramName;
841+
return escapeReservedWordUnapply(cp.baseName);
811842
} else {
812-
return cp.dataType + "Varr(" + cp.paramName + ")";
843+
return cp.dataType + "Varr(" + escapeReservedWordUnapply(cp.baseName) + ")";
813844
}
814845
} else {
815-
return cp.baseName + "Varr(" + cp.paramName + ")";
846+
return cp.baseName + "Varr(" + escapeReservedWordUnapply(cp.baseName) + ")";
816847
}
817848
}
818849

@@ -844,11 +875,23 @@ private String cpToQueryParameter(CodegenParameter cp, Set<String> imports, Map<
844875
}
845876

846877
vendorExtensions.putAll(refineProp(cp, imports));
847-
return cp.baseName + "QueryParam(" + cp.paramName + ")";
878+
return cp.baseName + "QueryParam(" + escapeReservedWordUnapply(cp.baseName) + ")";
848879
}
849880

850881
@Override
851882
public GeneratorLanguage generatorLanguage() {
852883
return GeneratorLanguage.SCALA;
853884
}
885+
886+
@Override
887+
protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
888+
return super.addMustacheLambdas()
889+
.put("escapeReservedWordUnapply", new EscapeKeywordLambda(this::escapeReservedWordUnapply));
890+
}
891+
892+
private String escapeReservedWordUnapply(String value) {
893+
// The unapply method doesn’t allow you to work with reserved variables via backticks;
894+
// in such cases you should use the variable via a placeholder instead.
895+
return isReservedWord(value) ? "_" + value : value;
896+
}
854897
}
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)