Skip to content
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

Add 'springdoc.trim-kotlin-indent' property to handle Kotlin multiline string indentation #2535

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,29 @@ public class SpringDocConfigProperties {
*/
private boolean nullableRequestParameterEnabled;

/**
* The trim kotlin indent.
*/
private boolean trimKotlinIndent;

/**
* Gets trim kotlin indent.
*
* @return the trim kotlin indent.
*/
public boolean isTrimKotlinIndent() {
return trimKotlinIndent;
}

/**
* Sets trim kotlin indent
*
* @param trimKotlinIndent the trim kotlin indent.
*/
public void setTrimKotlinIndent(boolean trimKotlinIndent) {
this.trimKotlinIndent = trimKotlinIndent;
}

/**
* Gets override with generic response.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@

package org.springdoc.core.utils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import io.swagger.v3.oas.models.SpecVersion;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.properties.SpringDocConfigProperties;
Expand Down Expand Up @@ -97,6 +99,9 @@ public String resolve(String parameterProperty, Locale locale) {
}
if (parameterProperty.equals(result))
try {
if (springDocConfigProperties.isTrimKotlinIndent()) {
parameterProperty = trimIndent(parameterProperty);
}
result = factory.resolveEmbeddedValue(parameterProperty);
}
catch (IllegalArgumentException ex) {
Expand All @@ -106,6 +111,44 @@ public String resolve(String parameterProperty, Locale locale) {
return result;
}

/**
* Returns a string where all leading indentation has been removed from each line.
* It detects the smallest common indentation of all the lines in the input string,
* and removes it.
*
* @param text The original string with possible leading indentation.
* @return The string with leading indentation removed from each line.
*/
private String trimIndent(String text) {
if (text == null) {
return null;
}
final String newLine = "\n";
String[] lines = text.split(newLine);
int minIndent = resolveMinIndent(lines);
return Arrays.stream(lines)
.map(line -> line.substring(Math.min(line.length(), minIndent)))
.reduce((a, b) -> a + newLine + b)
.orElse(StringUtils.EMPTY);
}

private int resolveMinIndent(String[] lines) {
return Arrays.stream(lines)
.filter(line -> !line.trim().isEmpty())
.mapToInt(this::countLeadingSpaces)
.min()
.orElse(0);
}

private int countLeadingSpaces(String line) {
int count = 0;
for (char ch : line.toCharArray()) {
if (ch != ' ') break;
count++;
}
return count;
}

/**
* Gets factory.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package test.org.springdoc.api.app11

import io.swagger.v3.oas.annotations.Operation
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
class ExampleController {

@Operation(
summary = "foo api",
description = """
this api is foo

#### Custom exception case
| Http Status | Error Code | Error Message | Error Data | Remark |
|-------------|-------------|--------------|------------|-----------|
| 403 | NO_PERMISSION |This request is only available to administrators. | | |
| 400 | STORE_NOT_FOUND |Store not found. | | |
"""
)
@GetMapping("/foo/remove-kotlin-indent")
fun readFoo(@RequestParam name: String?) = FooResponse("Hello ${name ?: "world"}")

}

data class FooResponse(
private val name: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* * Copyright 2019-2023 the original author or authors.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * https://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/

package test.org.springdoc.api.app11

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.test.context.TestPropertySource
import test.org.springdoc.api.AbstractKotlinSpringDocMVCTest

@TestPropertySource(properties = ["springdoc.trim-kotlin-indent=true"])
class SpringDocApp11Test : AbstractKotlinSpringDocMVCTest() {
@SpringBootApplication
@ComponentScan(basePackages = ["org.springdoc", "test.org.springdoc.api.app11"])
class DemoApplication
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"openapi": "3.0.1",
"info": {
"title": "OpenAPI definition",
"version": "v0"
},
"servers": [
{
"url": "http://localhost",
"description": "Generated server url"
}
],
"paths": {
"/foo/remove-kotlin-indent": {
"get": {
"tags": [
"example-controller"
],
"summary": "foo api",
"description": "\nthis api is foo\n\n#### Custom exception case\n| Http Status | Error Code | Error Message | Error Data | Remark |\n|-------------|-------------|--------------|------------|-----------|\n| 403 | NO_PERMISSION |This request is only available to administrators. | | |\n| 400 | STORE_NOT_FOUND |Store not found. | | |\n",
"operationId": "readFoo",
"parameters": [
{
"name": "name",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/FooResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"FooResponse": {
"type": "object",
"properties": {
"name": {
"type": "string",
"writeOnly": true
}
}
}
}
}
}