Skip to content

Commit 88286e2

Browse files
committed
Update mustache files for Kotlin Spring
1 parent 22c1439 commit 88286e2

4 files changed

Lines changed: 84 additions & 13 deletions

File tree

modules/openapi-generator/src/main/resources/kotlin-spring/api.mustache

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import io.swagger.v3.oas.annotations.security.*
1111
{{/swagger2AnnotationLibrary}}
1212
{{#swagger1AnnotationLibrary}}
1313
import io.swagger.annotations.Api
14+
{{#implicitHeaders}}
15+
import io.swagger.annotations.ApiImplicitParam
16+
import io.swagger.annotations.ApiImplicitParams
17+
{{/implicitHeaders}}
1418
import io.swagger.annotations.ApiOperation
1519
import io.swagger.annotations.ApiParam
1620
import io.swagger.annotations.ApiResponse
@@ -84,6 +88,9 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v
8488
authorizations = [{{#authMethods}}Authorization(value = "{{name}}"{{#isOAuth}}, scopes = [{{#scopes}}AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{^-last}}, {{/-last}}{{/scopes}}]{{/isOAuth}}){{^-last}}, {{/-last}}{{/authMethods}}]{{/hasAuthMethods}})
8589
@ApiResponses(
8690
value = [{{#responses}}ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{.}}}::class{{/baseType}}{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}},{{/-last}}{{/responses}}]){{/swagger1AnnotationLibrary}}
91+
{{#implicitHeadersParams.0}}
92+
{{>implicitHeaders}}
93+
{{/implicitHeadersParams.0}}
8794
{{#vendorExtensions.x-operation-extra-annotation}}
8895
{{{.}}}
8996
{{/vendorExtensions.x-operation-extra-annotation}}

modules/openapi-generator/src/main/resources/kotlin-spring/apiInterface.mustache

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import io.swagger.v3.oas.annotations.security.*
1616
{{/swagger2AnnotationLibrary}}
1717
{{#swagger1AnnotationLibrary}}
1818
import io.swagger.annotations.Api
19+
{{#implicitHeaders}}
20+
import io.swagger.annotations.ApiImplicitParam
21+
import io.swagger.annotations.ApiImplicitParams
22+
{{/implicitHeaders}}
1923
import io.swagger.annotations.ApiOperation
2024
import io.swagger.annotations.ApiParam
2125
import io.swagger.annotations.ApiResponse
@@ -99,6 +103,9 @@ interface {{classname}} {
99103
@ApiResponses(
100104
value = [{{#responses}}ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{.}}}::class{{/baseType}}{{#containerType}}, responseContainer = "{{{.}}}"{{/containerType}}){{^-last}}, {{/-last}}{{/responses}}]
101105
){{/swagger1AnnotationLibrary}}
106+
{{#implicitHeadersParams.0}}
107+
{{>implicitHeaders}}
108+
{{/implicitHeadersParams.0}}
102109
{{#vendorExtensions.x-operation-extra-annotation}}
103110
{{{.}}}
104111
{{/vendorExtensions.x-operation-extra-annotation}}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{{#swagger2AnnotationLibrary}}
2+
@Parameters(value = [
3+
{{#implicitHeadersParams}}
4+
Parameter(name = "{{{baseName}}}"{{#isDeprecated}}, deprecated = true{{/isDeprecated}}, description = "{{{description}}}"{{#required}}, required = true{{/required}}, `in` = ParameterIn.HEADER){{^-last}},{{/-last}}
5+
{{/implicitHeadersParams}}
6+
])
7+
{{/swagger2AnnotationLibrary}}
8+
{{#swagger1AnnotationLibrary}}
9+
@ApiImplicitParams(value = [
10+
{{#implicitHeadersParams}}
11+
ApiImplicitParam(name = "{{{baseName}}}", value = "{{{description}}}", {{#required}}required = true,{{/required}} dataType = "{{{dataType}}}", paramType = "header"){{^-last}},{{/-last}}
12+
{{/implicitHeadersParams}}
13+
])
14+
{{/swagger1AnnotationLibrary}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/spring/KotlinSpringServerCodegenTest.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import java.util.Map;
4040
import java.util.function.Consumer;
4141
import java.util.function.Function;
42+
import java.util.regex.Matcher;
43+
import java.util.regex.Pattern;
4244
import java.util.stream.Collectors;
4345
import java.util.stream.Stream;
4446

@@ -3831,7 +3833,7 @@ public void customPageableSchemaNotOverridden_issue13052() throws Exception {
38313833
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
38323834
additionalProperties.put(INTERFACE_ONLY, "true");
38333835
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
3834-
3836+
38353837
Map<String, File> files = generateFromContract("src/test/resources/bugs/issue_13052.yaml", additionalProperties);
38363838

38373839
// Custom Pageable model should be used instead of Spring's Pageable
@@ -3849,7 +3851,7 @@ public void springPaginatedWithNoDocumentationProvider() throws Exception {
38493851
additionalProperties.put(DOCUMENTATION_PROVIDER, "none");
38503852
additionalProperties.put(INTERFACE_ONLY, "true");
38513853
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
3852-
3854+
38533855
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
38543856

38553857
// Pageable should be added but no annotation imports
@@ -3912,7 +3914,7 @@ public void springPaginatedNoParamsNoContext() throws Exception {
39123914
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
39133915
additionalProperties.put(INTERFACE_ONLY, "true");
39143916
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
3915-
3917+
39163918
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
39173919

39183920
// Test operation listAllPets which has no parameters except pageable
@@ -4018,15 +4020,15 @@ public void springPaginatedMixedOperations() throws Exception {
40184020
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
40194021
additionalProperties.put(INTERFACE_ONLY, "true");
40204022
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
4021-
4023+
40224024
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
40234025

40244026
File petApi = files.get("PetApi.kt");
4025-
4027+
40264028
// Operation with x-spring-paginated should have Pageable
40274029
assertFileContains(petApi.toPath(), "fun findPetsByStatus(");
40284030
assertFileContains(petApi.toPath(), "pageable: Pageable");
4029-
4031+
40304032
// Operation without x-spring-paginated should NOT have Pageable
40314033
assertFileContains(petApi.toPath(), "fun addPet(");
40324034
// Verify addPet doesn't have pageable (it has body param only)
@@ -4035,7 +4037,7 @@ public void springPaginatedMixedOperations() throws Exception {
40354037
content.indexOf("fun addPet("),
40364038
content.indexOf(")", content.indexOf("fun addPet(")) + 1
40374039
);
4038-
Assert.assertFalse(addPetMethod.contains("pageable"),
4040+
Assert.assertFalse(addPetMethod.contains("pageable"),
40394041
"addPet should not have pageable parameter");
40404042
}
40414043

@@ -4045,7 +4047,7 @@ public void springPaginatedWithServiceInterface() throws Exception {
40454047
additionalProperties.put(USE_TAGS, "true");
40464048
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
40474049
additionalProperties.put(SERVICE_INTERFACE, "true");
4048-
4050+
40494051
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
40504052

40514053
// Test that pageable is in service interface
@@ -4064,7 +4066,7 @@ public void springPaginatedParameterOrdering() throws Exception {
40644066
additionalProperties.put(INTERFACE_ONLY, "true");
40654067
additionalProperties.put(SKIP_DEFAULT_INTERFACE, "true");
40664068
additionalProperties.put(INCLUDE_HTTP_REQUEST_CONTEXT, "true");
4067-
4069+
40684070
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
40694071

40704072
// Verify exact parameter ordering: allParams -> request -> pageable
@@ -4075,12 +4077,12 @@ public void springPaginatedParameterOrdering() throws Exception {
40754077
int methodStart = content.indexOf("fun findPetsByStatus(");
40764078
int methodEnd = content.indexOf("): ResponseEntity", methodStart);
40774079
String methodSignature = content.substring(methodStart, methodEnd);
4078-
4080+
40794081
// Verify order: status param comes before request, request comes before pageable
40804082
int statusPos = methodSignature.indexOf("status:");
40814083
int requestPos = methodSignature.indexOf("request:");
40824084
int pageablePos = methodSignature.indexOf("pageable:");
4083-
4085+
40844086
Assert.assertTrue(statusPos > 0, "status parameter should exist");
40854087
Assert.assertTrue(requestPos > statusPos, "request should come after status");
40864088
Assert.assertTrue(pageablePos > requestPos, "pageable should come after request");
@@ -4093,7 +4095,7 @@ public void springPaginatedDelegateCallPassesPageable() throws Exception {
40934095
additionalProperties.put(DOCUMENTATION_PROVIDER, "springdoc");
40944096
additionalProperties.put(DELEGATE_PATTERN, "true");
40954097
additionalProperties.put(INTERFACE_ONLY, "false");
4096-
4098+
40974099
Map<String, File> files = generateFromContract("src/test/resources/3_0/spring/petstore-with-spring-pageable.yaml", additionalProperties);
40984100

40994101
// Verify that interface method calls delegate with pageable parameter
@@ -5009,6 +5011,47 @@ public void shouldDefaultToJackson3WhenSpringBoot4EnabledViaSetter() throws IOEx
50095011
assertFileNotContains(pomPath, "com.fasterxml.jackson.module");
50105012
assertFileNotContains(pomPath, "jackson-datatype-jsr310");
50115013
}
5012-
}
50135014

5015+
@Test
5016+
public void shouldAddParameterWithInHeaderWhenImplicitHeadersIsTrue() throws IOException {
5017+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
5018+
output.deleteOnExit();
50145019

5020+
OpenAPI openAPI = new OpenAPIParser()
5021+
.readLocation("src/test/resources/bugs/issue_14418.yaml", null, new ParseOptions()).getOpenAPI();
5022+
5023+
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
5024+
codegen.setLibrary(SPRING_BOOT);
5025+
codegen.setOutputDir(output.getAbsolutePath());
5026+
codegen.additionalProperties().put(KotlinSpringServerCodegen.INTERFACE_ONLY, "true");
5027+
codegen.additionalProperties().put(CodegenConstants.MODEL_PACKAGE, "xyz.model");
5028+
codegen.additionalProperties().put(CodegenConstants.API_PACKAGE, "xyz.controller");
5029+
codegen.additionalProperties().put(AbstractKotlinCodegen.IMPLICIT_HEADERS, "true");
5030+
5031+
ClientOptInput input = new ClientOptInput()
5032+
.openAPI(openAPI)
5033+
.config(codegen);
5034+
5035+
DefaultGenerator generator = new DefaultGenerator();
5036+
generator.setGenerateMetadata(false);
5037+
Map<String, File> files = generator.opts(input).generate().stream()
5038+
.collect(Collectors.toMap(File::getName, Function.identity()));
5039+
5040+
File testApi = files.get("TestApi.kt");
5041+
String content = Files.readString(testApi.toPath());
5042+
String methodPattern = "fun test\\s*\\(.*?\\)";
5043+
Pattern pattern = Pattern.compile(methodPattern);
5044+
5045+
Matcher matcher = pattern.matcher(content);
5046+
Assert.assertTrue(matcher.find(), "Method 'test' should be found in generated file");
5047+
5048+
String methodSignature = matcher.group();
5049+
Assert.assertFalse(methodSignature.contains("testHeader"),
5050+
"Header param 'testHeader' should NOT be in method signature when implicitHeaders=true");
5051+
5052+
Assert.assertTrue(content.contains("@Parameters"),
5053+
"@Parameters annotation should be present");
5054+
Assert.assertTrue(content.contains("testHeader"),
5055+
"Header name 'testHeader' should appear in the annotation");
5056+
}
5057+
}

0 commit comments

Comments
 (0)