From e60c51ab6caad6696b84813c23f7dad1db8549fc Mon Sep 17 00:00:00 2001 From: donilg Date: Thu, 10 Jul 2025 16:07:39 +0500 Subject: [PATCH] [scala][http4s] fix codegen for using reserved words in openapi --- .../languages/ScalaHttp4sServerCodegen.java | 44 ++++++++++++++++--- .../scala-http4s-server/delegateArgs.mustache | 10 ++--- .../delegateCallGeneric.mustache | 4 +- .../delegateCallJson.mustache | 4 +- .../scala-http4s-server/types.mustache | 4 +- .../scala/org/openapitools/apis/FakeApi.scala | 4 +- .../scala/org/openapitools/models/types.scala | 4 +- 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java index 934fa2b6bf55..8ea52fb83749 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java @@ -16,10 +16,14 @@ package org.openapitools.codegen.languages; +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Escapers; +import com.samskivert.mustache.Mustache; import io.swagger.v3.oas.models.media.Schema; import org.openapitools.codegen.*; import org.openapitools.codegen.meta.features.*; import org.openapitools.codegen.model.*; +import org.openapitools.codegen.templating.mustache.EscapeKeywordLambda; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -140,7 +144,7 @@ public ScalaHttp4sServerCodegen() { additionalProperties.put("infoEmail", "team@openapitools.org"); additionalProperties.put("licenseInfo", "Apache 2.0"); additionalProperties.put("licenseUrl", "http://apache.org/licenses/LICENSE-2.0.html"); - + additionalProperties.put("fnEscapeBacktick", new EscapeBacktickLambda()); languageSpecificPrimitives = new HashSet<>( Arrays.asList( @@ -557,7 +561,12 @@ public String getHelp() { @Override public String escapeReservedWord(String name) { - return "_" + name; + if (this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + // Reserved words will be further escaped at the mustache compiler level. + // Scala escaping done here (via `, without compiler escaping) would otherwise be HTML encoded. + return "`" + name + "`"; } @Override @@ -807,12 +816,12 @@ private String cpToPathParameter(CodegenParameter cp, Set imports, Map imports, Map< } vendorExtensions.putAll(refineProp(cp, imports)); - return cp.baseName + "QueryParam(" + cp.paramName + ")"; + return cp.baseName + "QueryParam(" + escapeReservedWordUnapply(cp.baseName) + ")"; } @Override public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.SCALA; } + + @Override + protected ImmutableMap.Builder addMustacheLambdas() { + return super.addMustacheLambdas() + .put("escapeReservedWordUnapply", new EscapeKeywordLambda(this::escapeReservedWordUnapply)); + } + + private String escapeReservedWordUnapply(String value) { + // The unapply method doesn’t allow you to work with reserved variables via backticks; + // in such cases you should use the variable via a placeholder instead. + return isReservedWord(value) ? "_" + value : value; + } + + private static class EscapeBacktickLambda extends AbstractScalaCodegen.CustomLambda { + @Override + public String formatFragment(String fragment) { + if (fragment.startsWith("`") && fragment.endsWith("`")) { + String unescaped = fragment.substring(1, fragment.length() - 1); + return "`" + Escapers.HTML.escape(unescaped) + "`"; + } + return Escapers.HTML.escape(fragment); + } + } } diff --git a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache index e3800779c38f..862521eb4e41 100644 --- a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache +++ b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache @@ -1,21 +1,21 @@ {{#pathParams}} - {{paramName}}: {{{vendorExtensions.x-type}}}, + {{#fnEscapeBacktick}}{{{paramName}}}{{/fnEscapeBacktick}}: {{{vendorExtensions.x-type}}}, {{/pathParams}} {{#queryParams}} {{#isArray}} {{#required}} - {{paramName}}: List[{{{items.vendorExtensions.x-type}}}], + {{#fnEscapeBacktick}}{{{paramName}}}{{/fnEscapeBacktick}}: List[{{{items.vendorExtensions.x-type}}}], {{/required}} {{^required}} - {{paramName}}: Option[List[{{{items.vendorExtensions.x-type}}}]], + {{#fnEscapeBacktick}}{{{paramName}}}{{/fnEscapeBacktick}}: Option[List[{{{items.vendorExtensions.x-type}}}]], {{/required}} {{/isArray}} {{^isArray}} {{#required}} - {{paramName}}: {{{vendorExtensions.x-type}}}, + {{#fnEscapeBacktick}}{{{paramName}}}{{/fnEscapeBacktick}}: {{{vendorExtensions.x-type}}}, {{/required}} {{^required}} - {{paramName}}: Option[{{{vendorExtensions.x-type}}}], + {{#fnEscapeBacktick}}{{{paramName}}}{{/fnEscapeBacktick}}: Option[{{{vendorExtensions.x-type}}}], {{/required}} {{/isArray}} {{/queryParams}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallGeneric.mustache b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallGeneric.mustache index c610b0a346b0..f216cb6f73ae 100644 --- a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallGeneric.mustache @@ -1,6 +1,6 @@ {{^authName}} -delegate.{{operationId}}.handle(req, {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses) +delegate.{{operationId}}.handle(req, {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses) {{/authName}} {{#authName}} -delegate.{{operationId}}.handle_{{authName}}(auth, req, {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses) +delegate.{{operationId}}.handle_{{authName}}(auth, req, {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses) {{/authName}} diff --git a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache index badf4b14a860..2505623c8ae1 100644 --- a/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache +++ b/modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache @@ -1,6 +1,6 @@ {{^authName}} - delegate.{{operationId}}.handle(req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses) + delegate.{{operationId}}.handle(req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/pathParams}}{{#queryParams}}{{#lambda.escapeReservedWordUnapply}}{{baseName}}{{/lambda.escapeReservedWordUnapply}}, {{/queryParams}}responses) {{/authName}} {{#authName}} - delegate.{{operationId}}.handle_{{authName}}(auth, req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{paramName}}, {{/pathParams}}{{#queryParams}}{{paramName}}, {{/queryParams}}responses) + 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) {{/authName}} diff --git a/modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache b/modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache index dab5265181dd..6e9f6042237e 100644 --- a/modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache +++ b/modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache @@ -23,7 +23,7 @@ import {{modelPackage}}.{{classname}}.{{classname}} /** * {{{description}}} {{#vars}} -* @param {{name}} {{{description}}} +* @param {{#fnEscapeBacktick}}{{{name}}}{{/fnEscapeBacktick}} {{{description}}} {{/vars}} */ {{#vendorExtensions.x-isSealedTrait}} @@ -104,7 +104,7 @@ object {{classname}} extends Enumeration { {{#vendorExtensions.x-another}} case class {{classname}}( {{#vars}} - {{name}}: {{^required}}Option[{{{vendorExtensions.x-type}}}]{{/required}}{{#required}}{{{vendorExtensions.x-type}}}{{/required}}{{^-last}},{{/-last}} + {{#fnEscapeBacktick}}{{{name}}}{{/fnEscapeBacktick}}: {{^required}}Option[{{{vendorExtensions.x-type}}}]{{/required}}{{#required}}{{{vendorExtensions.x-type}}}{{/required}}{{^-last}},{{/-last}} {{/vars}} ){{#vendorExtensions.x-extends}} extends {{.}}{{/vendorExtensions.x-extends}}{{#vendorExtensions.x-extendsWith}} with {{.}}{{/vendorExtensions.x-extendsWith}} object {{classname}} { diff --git a/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/apis/FakeApi.scala b/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/apis/FakeApi.scala index af317718272c..04cc33f8cc46 100644 --- a/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/apis/FakeApi.scala +++ b/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/apis/FakeApi.scala @@ -57,8 +57,8 @@ trait FakeApiDelegate[F[_]] { def handle( req: Request[F], - _type: String, - _var: Option[String], + `type`: String, + `var`: Option[String], responses: reservedWordsResponses[F] ): F[Response[F]] diff --git a/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/models/types.scala b/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/models/types.scala index 4db7ce57363f..87e8acd1305e 100644 --- a/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/models/types.scala +++ b/samples/server/petstore/scala-http4s-server/src/main/scala/org/openapitools/models/types.scala @@ -15,13 +15,13 @@ import java.time.ZonedDateTime /** * Describes the result of uploading an image resource * @param code -* @param _type +* @param `type` * @param message */ case class ApiResponse( code: Option[Int], - _type: Option[String], + `type`: Option[String], message: Option[String] ) object ApiResponse {