Skip to content

Commit a98f1bd

Browse files
committed
fix(kotlin): expose raw fields and use escaping lambdas in templates
- Add unescapedSummary, unescapedMessage, unescapedDefaultValue, unescapedDescription fields to CodegenOperation, CodegenResponse, CodegenParameter, CodegenProperty; populate in DefaultCodegen - Add escapeInNormalString, escapeDollarInMultiline, escapeDollar, removeLineBreak lambdas to AbstractKotlinCodegen/KotlinSpringServerCodegen via addMustacheLambdas() using lambda. prefix - Update ~50 Mustache templates across kotlin-spring, kotlin-client, kotlin-server, kotlin-misk, kotlin-wiremock to apply lambdas at point-of-use instead of relying on pre-escaped values - Fix $ in PATH const vals, special chars in annotation strings, and */ corruption in string literal defaults
1 parent aa834b9 commit a98f1bd

54 files changed

Lines changed: 184 additions & 81 deletions

File tree

Some content is hidden

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class CodegenOperation {
3333
hasErrorResponseObject; // if 4xx, 5xx responses have at least one error object defined
3434
public CodegenProperty returnProperty;
3535
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType,
36-
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
36+
returnContainer, summary, unescapedSummary, unescapedNotes, notes, baseName, defaultResponse;
3737
public CodegenDiscriminator discriminator;
3838
public List<Map<String, String>> consumes, produces, prioritizedContentTypes;
3939
public List<CodegenServer> servers = new ArrayList<CodegenServer>();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class CodegenParameter implements IJsonSchemaValidationProperties {
3434
isCollectionFormatMulti, isPrimitiveType, isModel, isExplode, isDeepObject, isMatrix, isAllowEmptyValue,
3535
isFormStyle, isSpaceDelimited, isPipeDelimited;
3636
public String baseName, paramName, dataType, datatypeWithEnum, dataFormat, contentType,
37-
collectionFormat, description, unescapedDescription, baseType, defaultValue, enumDefaultValue, enumName, style;
37+
collectionFormat, description, unescapedDescription, baseType, defaultValue, unescapedDefaultValue, enumDefaultValue, enumName, style;
3838

3939
public String nameInLowerCase; // property name in lower case
4040
public String nameInCamelCase; // property name in camel case (e.g. modifiedDate)

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ public class CodegenProperty implements Cloneable, IJsonSchemaValidationProperti
8181
@Getter @Setter
8282
public String unescapedDescription;
8383

84+
/**
85+
* The default value string without escape characters; use this in string literal contexts
86+
* (e.g. inside "..." or """...""") together with an appropriate escaping lambda.
87+
* Unlike {@code defaultValue}, this is never passed through escapeText() or escapeUnsafeCharacters().
88+
*/
89+
@Getter @Setter
90+
public String unescapedDefaultValue;
91+
8492
/**
8593
* maxLength validation for strings, see http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1
8694
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class CodegenResponse implements IJsonSchemaValidationProperties {
3333
public boolean is4xx;
3434
public boolean is5xx;
3535
public String message;
36+
public String unescapedMessage;
3637
public List<Map<String, Object>> examples;
3738
public String dataType;
3839
public String baseType;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4315,6 +4315,7 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
43154315

43164316
// set the default value
43174317
property.defaultValue = toDefaultValue(property, p);
4318+
property.unescapedDefaultValue = p.getDefault() != null ? String.valueOf(p.getDefault()) : null;
43184319
property.defaultValueWithParam = toDefaultValueWithParam(name, p);
43194320

43204321
LOGGER.debug("debugging from property return: {}", property);
@@ -4738,6 +4739,7 @@ public CodegenOperation fromOperation(String path,
47384739
}
47394740

47404741
op.summary = escapeText(operation.getSummary());
4742+
op.unescapedSummary = operation.getSummary();
47414743
op.unescapedNotes = operation.getDescription();
47424744
op.notes = escapeText(operation.getDescription());
47434745
op.hasConsumes = false;
@@ -5092,6 +5094,7 @@ public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
50925094
}
50935095
r.schema = responseSchema;
50945096
r.message = escapeText(response.getDescription());
5097+
r.unescapedMessage = response.getDescription();
50955098

50965099
// adding examples to API responses
50975100
Map<String, Example> examples = ExamplesUtils.getExamplesFromResponse(openAPI, response);
@@ -5641,6 +5644,7 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
56415644

56425645
// set default value
56435646
codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, parameterSchema);
5647+
codegenParameter.unescapedDefaultValue = parameterSchema.getDefault() != null ? String.valueOf(parameterSchema.getDefault()) : null;
56445648

56455649
finishUpdatingParameter(codegenParameter, parameter);
56465650
return codegenParameter;
@@ -7543,6 +7547,7 @@ public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set
75437547

75447548
// set default value
75457549
codegenParameter.defaultValue = toDefaultParameterValue(codegenProperty, propertySchema);
7550+
codegenParameter.unescapedDefaultValue = propertySchema.getDefault() != null ? String.valueOf(propertySchema.getDefault()) : null;
75467551

75477552
if (ModelUtils.isFileSchema(ps) && !ModelUtils.isStringSchema(ps)) {
75487553
// swagger v2 only, type file
@@ -8089,6 +8094,7 @@ public CodegenParameter fromRequestBody(RequestBody body, Set<String> imports, S
80898094
codegenParameter.baseName = "UNKNOWN_BASE_NAME";
80908095
codegenParameter.paramName = "UNKNOWN_PARAM_NAME";
80918096
codegenParameter.description = escapeText(body.getDescription());
8097+
codegenParameter.unescapedDescription = body.getDescription();
80928098
codegenParameter.required = body.getRequired() != null ? body.getRequired() : Boolean.FALSE;
80938099
codegenParameter.isBodyParam = Boolean.TRUE;
80948100
if (body.getExtensions() != null) {

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,20 @@ protected void updateModelForObject(CodegenModel m, Schema schema) {
12561256
@Override
12571257
protected ImmutableMap.Builder<String, Mustache.Lambda> addMustacheLambdas() {
12581258
return super.addMustacheLambdas()
1259-
.put("escapeDollar", new EscapeChar("(?<!\\\\)\\$", "\\\\\\$"));
1259+
.put("escapeDollar", new EscapeChar("(?<!\\\\)\\$", "\\\\\\$"))
1260+
// Replaces each $ with ${'$'} for use inside """...""" triple-quoted Kotlin strings,
1261+
// where backslash escapes are not available.
1262+
.put("escapeDollarInMultiline", new EscapeChar("\\$", "\\${'\\$'}"))
1263+
// Full escaping for raw values going into "..." double-quoted Kotlin strings.
1264+
// Handles \, $, " and also literal newline/tab/carriage-return chars.
1265+
// \\ replacement must be first to avoid double-escaping subsequent insertions.
1266+
.put("escapeInNormalString", (fragment, writer) -> writer.write(fragment.execute()
1267+
.replace("\\", "\\\\")
1268+
.replace("$", "\\$")
1269+
.replace("\"", "\\\"")
1270+
.replace("\n", "\\n")
1271+
.replace("\r", "\\r")
1272+
.replace("\t", "\\t")));
12601273
}
12611274

12621275
protected interface DataTypeAssigner {

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -957,17 +957,13 @@ public void processOpts() {
957957
// spring uses the jackson lib, and we disallow configuration.
958958
additionalProperties.put("jackson", "true");
959959

960-
// add lambda for mustache templates
961-
additionalProperties.put("lambdaEscapeInNormalString",
962-
(Mustache.Lambda) (fragment, writer) -> writer.write(fragment.execute().replaceAll("([$\"\\\\])", "\\\\$1")));
963-
additionalProperties.put("lambdaRemoveLineBreak",
964-
(Mustache.Lambda) (fragment, writer) -> writer.write(fragment.execute().replaceAll("[\\r\\n]", "")));
965960
}
966961

967962
@Override
968963
protected ImmutableMap.Builder<String, Lambda> addMustacheLambdas() {
969964
return super.addMustacheLambdas()
970-
.put("escapeDoubleQuote", new EscapeLambda("\"", "\\\""));
965+
.put("escapeDoubleQuote", new EscapeLambda("\"", "\\\""))
966+
.put("removeLineBreak", (fragment, writer) -> writer.write(fragment.execute().replaceAll("[\\r\\n]", "")));
971967
}
972968

973969
@Override

modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
@SerializedName("{{{vendorExtensions.x-base-name-literal}}}")
1010
{{/gson}}
1111
{{#jackson}}
12-
@get:JsonProperty("{{{vendorExtensions.x-base-name-literal}}}")
12+
@get:JsonProperty("{{#lambda.escapeInNormalString}}{{{vendorExtensions.x-base-name-literal}}}{{/lambda.escapeInNormalString}}")
1313
{{/jackson}}
1414
{{#kotlinx_serialization}}
1515
{{^isEnum}}{{^isArray}}{{^isPrimitiveType}}{{^isModel}}@Contextual {{/isModel}}{{/isPrimitiveType}}{{/isArray}}{{/isEnum}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}")
@@ -21,4 +21,4 @@
2121
{{#deprecated}}
2222
@Deprecated(message = "This property is deprecated.")
2323
{{/deprecated}}
24-
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInPascalCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInPascalCase}}}{{/isArray}}{{/isEnum}}? = {{^defaultValue}}null{{/defaultValue}}{{#defaultValue}}{{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/defaultValue}}
24+
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInPascalCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInPascalCase}}}{{/isArray}}{{/isEnum}}? = {{^defaultValue}}null{{/defaultValue}}{{#defaultValue}}{{#isString}}{{^isEnum}}"{{#lambda.escapeInNormalString}}{{{unescapedDefaultValue}}}{{/lambda.escapeInNormalString}}"{{/isEnum}}{{#isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isString}}{{^isString}}{{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/isString}}{{/defaultValue}}

modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
@SerializedName("{{{vendorExtensions.x-base-name-literal}}}")
1010
{{/gson}}
1111
{{#jackson}}
12-
@get:JsonProperty("{{{vendorExtensions.x-base-name-literal}}}")
12+
@get:JsonProperty("{{#lambda.escapeInNormalString}}{{{vendorExtensions.x-base-name-literal}}}{{/lambda.escapeInNormalString}}")
1313
{{/jackson}}
1414
{{#kotlinx_serialization}}
1515
{{^isEnum}}{{^isArray}}{{^isPrimitiveType}}{{^isModel}}@Contextual {{/isModel}}{{/isPrimitiveType}}{{/isArray}}{{/isEnum}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}")
@@ -21,4 +21,4 @@
2121
{{#deprecated}}
2222
@Deprecated(message = "This property is deprecated.")
2323
{{/deprecated}}
24-
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") @Required {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInPascalCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInPascalCase}}}{{/isArray}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/defaultValue}}
24+
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") @Required {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInPascalCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInPascalCase}}}{{/isArray}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}{{#defaultValue}} = {{#isString}}{{^isEnum}}"{{#lambda.escapeInNormalString}}{{{unescapedDefaultValue}}}{{/lambda.escapeInNormalString}}"{{/isEnum}}{{#isEnum}}{{{defaultValue}}}{{/isEnum}}{{/isString}}{{^isString}}{{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/isString}}{{/defaultValue}}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#isFormParam}}{{^isFile}}{{#isMultipart}}@Part{{/isMultipart}}{{^isMultipart}}@Field{{/isMultipart}}("{{baseName}}") {{{paramName}}}: {{{dataType}}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}{{/isFile}}{{#isFile}}{{#isMultipart}}@Part{{/isMultipart}}{{^isMultipart}}@Field("{{baseName}}"){{/isMultipart}} {{{paramName}}}: {{#isCollectionFormatMulti}}List<{{/isCollectionFormatMulti}}MultipartBody.Part{{#isCollectionFormatMulti}}>{{/isCollectionFormatMulti}}{{^required}}? = null{{/required}}{{/isFile}}{{/isFormParam}}
1+
{{#isFormParam}}{{^isFile}}{{#isMultipart}}@Part{{/isMultipart}}{{^isMultipart}}@Field{{/isMultipart}}("{{baseName}}") {{{paramName}}}: {{{dataType}}}{{#required}}{{#defaultValue}} = {{^isNumber}}{{#isString}}{{^allowableValues}}"{{#lambda.escapeInNormalString}}{{{unescapedDefaultValue}}}{{/lambda.escapeInNormalString}}"{{/allowableValues}}{{#allowableValues}}{{{defaultValue}}}{{/allowableValues}}{{/isString}}{{^isString}}{{{defaultValue}}}{{/isString}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{/required}}{{^required}}?{{#defaultValue}} = {{^isNumber}}{{#isString}}{{^allowableValues}}"{{#lambda.escapeInNormalString}}{{{unescapedDefaultValue}}}{{/lambda.escapeInNormalString}}"{{/allowableValues}}{{#allowableValues}}{{{defaultValue}}}{{/allowableValues}}{{/isString}}{{^isString}}{{{defaultValue}}}{{/isString}}{{/isNumber}}{{#isNumber}}{{{dataType}}}("{{{defaultValue}}}"){{/isNumber}}{{/defaultValue}}{{^defaultValue}} = null{{/defaultValue}}{{/required}}{{/isFile}}{{#isFile}}{{#isMultipart}}@Part{{/isMultipart}}{{^isMultipart}}@Field("{{baseName}}"){{/isMultipart}} {{{paramName}}}: {{#isCollectionFormatMulti}}List<{{/isCollectionFormatMulti}}MultipartBody.Part{{#isCollectionFormatMulti}}>{{/isCollectionFormatMulti}}{{^required}}? = null{{/required}}{{/isFile}}{{/isFormParam}}

0 commit comments

Comments
 (0)