[BUG] Kotlin server does not escape special character ('$') in jackson annotation
Bug Report Checklist
- [x] Have you provided a full/minimal spec to reproduce the issue?
- [x] Have you validated the input using an OpenAPI validator (example)?
- [x] Have you tested with the latest master to confirm the issue still exists?
- [x] Have you searched for related issues/PRs?
- [x] What's the actual output vs expected output?
- [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description
The given schema component with a special character in a property name.
(Example: "Group" schema from SCIM)
Using the kotlin server generator with jackson serializer generates invalid code
data class BaseGroupMembersInner (
/* The `id` of the member. Each value MUST be the `id` of the corresponding `User` resource, which is unique across the service provider's entire set of resources. */
@JsonProperty("value")
val `value`: java.util.UUID,
/* The URI of the corresponding `User` resource. Nullable for requests but required for responses. */
@JsonProperty("$ref")
val dollarRef: kotlin.String? = null
)
It should escape the special character ($)
@JsonProperty("\$ref")
openapi-generator version
7.11.0
OpenAPI declaration file content or url
openapi: 3.1.0
info:
version: 5.0.5
title: Internal Users SCIM
description: ...
tags:
- name: Groups
description: ...
paths:
/v3/internal/scim/Groups/{id}:
get:
summary: 'Get group by id'
description: ...
externalDocs:
url: 'https://datatracker.ietf.org/doc/html/rfc7644#section-3.2'
description: 'RFC 7644, Section 3.2'
operationId: getGroup
tags:
- Groups
parameters:
- $ref: '#/components/parameters/GroupIdInPath'
responses:
'200':
description: Successful response
content:
application/scim+json:
schema:
$ref: '#/components/schemas/Group'
application/json:
schema:
$ref: '#/components/schemas/Group'
components:
parameters:
GroupIdInPath:
name: id
in: path
description: |
The unique identifier of a group
required: true
schema:
type: string
format: uuid
schemas:
Meta:
type: object
description: ...
externalDocs:
url: 'https://datatracker.ietf.org/doc/html/rfc7643#section-3.1'
description: 'RFC 7643, Section 3.1'
properties:
resourceType:
type: string
description: ...
examples:
- 'ResourceType'
created:
type: string
format: date-time
description: ...
examples:
- '2024-07-21T17:32:28Z'
lastModified:
type: string
format: date-time
description: ...
location:
type: string
description: ...
version:
type: string
description: ...
BaseGroup:
type: object
description: ...
externalDocs:
url: 'https://datatracker.ietf.org/doc/html/rfc7643#section-4'
description: 'RFC 7643, Section 4'
required:
- members
properties:
members:
type: array
description: ...
items:
type: object
required:
- value
properties:
value:
type: string
format: uuid
description: ...
$ref:
type:
- "string"
- "null"
description: ...
default: [ ] # default is for `PUT` request
Group:
type: object
description: ...
externalDocs:
url: 'https://datatracker.ietf.org/doc/html/rfc7643#section-4'
description: 'RFC 7643, Section 4'
required:
- schemas
- id
- displayName
- members
allOf:
- type: object
properties:
schemas:
type: array
description: ...
items:
type: string
id:
type: string
format: uuid
readOnly: true
description: ...
displayName:
type: string
readOnly: true
description: ...
- $ref: '#/components/schemas/BaseGroup'
Generation Details
Gradle configuration
openApiGenerate {
generatorName = "kotlin-server"
inputSpec = "${rootProject.projectDir}/rest-application/src/main/resources/test-petstore-api.yml"
library.convention("jaxrs-spec")
outputDir.convention("${project.layout.buildDirectory.get()}/generated-rest/server/scim")
apiPackage.convention("com.toasttab.service.scim.api")
modelPackage.convention("com.toasttab.service.scim.models")
packageName.convention("com.toasttab.service.scim")
additionalProperties.convention(
mapOf(
"collectionType" to "list",
"enumPropertyNaming" to "UPPERCASE",
"dateLibrary" to "java8",
"interfaceOnly" to "true",
"returnResponse" to "true"
)
)
}
Steps to reproduce
- Define a schema wit a property name of
$ref - Configure opernapi generator with
generatorName = "kotlin-server"ANDlibrary.convention("jaxrs-spec") - Attempt to generate server side code
Related issues/PRs
Suggest a fix
Good Day, @jasonfagerberg-toast ,
Please can you confirm if you're using kotlin-server or the client version, kotlin?
Also, I tried with version 7.11 on my end with kotlin and it generated a backslash as expected
data class BaseGroupMembersInner (
/* ... */
@get:JsonProperty("value")
val `value`: java.util.UUID,
/* ... */
@get:JsonProperty("\$ref")
val dollarRef: kotlin.String? = null
) {
Hey @DavidGrath! Thank you for looking into than and sorry this is totally my fault, got lost when bumping around branches and gave you the wrong gradle config. Here is one that actually reproduces the error
openApiGenerate {
generatorName = "kotlin-server"
inputSpec = "${rootProject.projectDir}/rest-application/src/main/resources/test-petstore-api.yml"
library.convention("jaxrs-spec")
outputDir.convention("${project.layout.buildDirectory.get()}/generated-rest/server/scim")
apiPackage.convention("com.toasttab.service.scim.api")
modelPackage.convention("com.toasttab.service.scim.models")
packageName.convention("com.toasttab.service.scim")
additionalProperties.convention(
mapOf(
"serializationLibrary" to "jackson",
"collectionType" to "list",
"enumPropertyNaming" to "UPPERCASE",
"dateLibrary" to "java8",
"interfaceOnly" to "true",
"returnResponse" to "true"
)
)
}
Ill update the issue body to reflect the correct config. I will put the incorrect config here for posterity
Initial Incorrect config (fixed now)
openApiGenerate {
generatorName = "kotlin"
inputSpec = "${rootProject.projectDir}/.toast/schemas/internal-scim-api.yaml"
outputDir = "${getLayout().buildDirectory.get()}/generated/openapi"
modelPackage = "com.toasttab.service.users.v3.internal.scim.models"
additionalProperties.putAll(
mapOf(
"serializationLibrary" to "jackson",
"collectionType" to "list",
"enumPropertyNaming" to "UPPERCASE"
)
)
configOptions = mapOf(
"identifierNamingConvention" to "original"
)
}
@jasonfagerberg-toast , I see there's no Jackson in the kotlin-server generator, and so there's no $ref in an annotation, either, please can you confirm if there's still an issue with your use case?
@Serializable
data class BaseGroupMembersInner(
/* ... */
val `value`: java.util.UUID,
/* ... */
val dollarRef: kotlin.String? = null
)
@DavidGrath so sorry for the confusion! It seems that the serializationLibrary doesn't seem to be needed at all, however jaxrs-spec server codegen implicitly uses jackson
https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/kotlin-server/libraries/jaxrs-spec/data_class.mustache#L61
@jasonfagerberg-toast Thank you! So sorry I missed that, I actually didn't specify JAXRS so it defaulted to Ktor.
I found out that there's a lambda called escapeDollar that seems to be for this specific issue. It was already used in the kotlin-client api files:
https://github.com/OpenAPITools/openapi-generator/blob/3c664d1a59717a3ba7e56c3584b8b2648b347bbf/modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache#L202
So maybe it was just overlooked
Although kotlin-client by itself uses a different solution to this issue with a vendor extension:
https://github.com/OpenAPITools/openapi-generator/blob/3c664d1a59717a3ba7e56c3584b8b2648b347bbf/modules/openapi-generator/src/main/resources/kotlin-client/data_class_opt_var.mustache#L12
And that vendor extension apparently does the exact same thing:
https://github.com/OpenAPITools/openapi-generator/blob/3c664d1a59717a3ba7e56c3584b8b2648b347bbf/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java#L926
I'll run the necessary tests and make a PR soon
@e5l Good Day, I see you're on the technical committee for Kotlin, so I wanted to add you to this discussion
I found out that this issue of dollar escape not being used actually extends to other files in the kotlin-server and kotlin-client folders:
For example, with kotlin-server and javalin5:
package com.toasttab.service.scim.models
/**
*
* @param `value` ...
* @param dollarRef ...
*/
data class BaseGroupMembersInner(
/* ... */
@field:com.fasterxml.jackson.annotation.JsonProperty("value")
val `value`: java.util.UUID,
/* ... */
@field:com.fasterxml.jackson.annotation.JsonProperty("$ref")
val dollarRef: kotlin.String? = null
)
I plan to wrap all usages of baseName with the escapeDollar lambda within these folders
Do you have any thoughts on this?
@4brunu , please what are your thoughts?
@jasonfagerberg-toast it's fixed now
As far as kotlin-spring is concerned, I tried to do as thorough job as possible here: #22449