Skip to content

trim-kotlin-indent function also applies to Schema annotation #2645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
e-build opened this issue Jul 12, 2024 · 2 comments
Closed

trim-kotlin-indent function also applies to Schema annotation #2645

e-build opened this issue Jul 12, 2024 · 2 comments

Comments

@e-build
Copy link
Contributor

e-build commented Jul 12, 2024

Is your feature request related to a problem? Please describe.
I added the trim-kotlin-indent feature in the last PR. Mainly, we added logic to remove Kotlin indents for strings in the Operation annotation properties. After checking the issues that arose, I found that I wanted to apply the @Schema annotation as well (PR), so I'm going to work on it.

Describe the solution you'd like
I will also add logic to remove indentation for property values ​​of @Schema

@GeorgEchterling
Copy link

I've previously implemented this as a GlobalOpenApiCustomizer:

@Configuration
class OpenApiConfig {
    /**
     * Customizes the OpenAPI specification that was generated from Swagger annotations.
     * @see formatAllTexts
     * @see reorderAllFields
     */
    @Bean
    fun openApiCustomizer() = GlobalOpenApiCustomizer { openapi ->
        formatAllTexts(openapi)
        reorderAllFields(openapi)
    }

    /**
     * Trims Kotlin's indent on relevant specification fields, since trimIndent cannot be used directly in annotations.
     * This way, we can comfortably use multiline strings in annotations without
     * either formatting errors or having to start at column 1.
     */
    private fun formatAllTexts(openapi: OpenAPI) {
        openapi.components?.schemas?.values?.forEach(this::formatSchema)
        openapi.paths?.values?.forEach { path ->
            path.description = path.description?.trimIndent()
            path.readOperations()?.forEach(this::formatOperation)
        }
        openapi.components?.securitySchemes?.values?.forEach { securityScheme ->
            // @SecurityScheme.descriptions
            securityScheme.description = securityScheme.description?.trimIndent()
        }
        openapi.tags?.forEach { tag ->
            // @Tag.description
            tag.description = tag.description?.trimIndent()
        }
    }

    /**
     * Recursively formats the [schema]'s descriptions and examples.
     */
    private fun formatSchema(schema: Schema<*>) {
        // @Schema.description
        schema.description = schema.description?.trimIndent()

        // @Schema.example
        val example: Any? = schema.example
        if (example is String) schema.example = example.trimIndent()

        // Properties on classes
        schema.properties?.values?.forEach(this::formatSchema)

        // @ArraySchema.schema
        schema.items?.apply(this::formatSchema)
    }

    /**
     * Formats the [operation]'s descriptions.
     */
    private fun formatOperation(operation: Operation) {
        // @Operation.description
        operation.description = operation.description?.trimIndent()
        operation.responses?.values?.forEach { response ->
            // @ApiResponse.description
            response.description = response.description?.trimIndent()
        }
    }

    /**
     * Puts any inconsistently ordered fields in the generated OpenAPI specification into a consistent order.
     */
    private fun reorderAllFields(openapi: OpenAPI) {
        openapi.paths?.values?.forEach { path ->
            path.description = path.description?.trimIndent()
            path.readOperations()?.forEach(this::reorderApiResponses)
        }
    }

    /**
     * Reorders the [operation]'s API responses by status code.
     * The original generated responses were ordered arbitrarily and may change on any restart.
     */
    private fun reorderApiResponses(operation: Operation) {
        val originalResponses = operation.responses ?: return

        val reorderedResponses = ApiResponses()
        for ((name, item) in originalResponses.toSortedMap()) {
            reorderedResponses[name] = item
        }

        reorderedResponses.extensions = originalResponses.extensions

        operation.responses = reorderedResponses
    }
}

@e-build
Copy link
Contributor Author

e-build commented Sep 9, 2024

@GeorgEchterling
I already know that trim-kotlin-indent can be implemented using GlobalOpenApiCustomizer. I also personally solved the problem by implementing GlobalOpenApiCustomizer. However, I thought that all users using Kotlin would experience the same problem and feel the same inconvenience every time, so I added the function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants