Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|autoXSpringPaginated|Automatically add x-spring-paginated to operations that have the given set of pagination query parameters. 'page-size-sort' requires 'page', 'size', and 'sort'; 'page-size' requires only 'page' and 'size' (sort may be absent). Operations with x-spring-paginated explicitly set to false will not be auto-detected. The legacy values 'true' (alias for 'page-size-sort') and 'false' (alias for 'none') are deprecated and will be removed in a future release.|<dl><dt>**none**</dt><dd>Disable auto-detection.</dd><dt>**page-size-sort**</dt><dd>Require 'page', 'size', and 'sort' query parameters.</dd><dt>**page-size**</dt><dd>Require only 'page' and 'size' query parameters.</dd><dt>**true**</dt><dd>(deprecated) alias for 'page-size-sort'.</dd><dt>**false**</dt><dd>(deprecated) alias for 'none'.</dd></dl>|none|
|basePackage|base package (invokerPackage) for generated code| |org.openapitools|
|beanQualifiers|Whether to add fully-qualifier class names as bean qualifiers in @Component and @RestController annotations. May be used to prevent bean names clash if multiple generated libraries (contexts) added to single project.| |false|
|clientRegistrationId|Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation). Requires library=spring-declarative-http-interface and useSpringBoot4=true (Spring Security 7).| |null|
|companionObject|Whether to generate companion objects in data classes, enabling companion extensions.| |false|
|configPackage|configuration package for generated code| |org.openapitools.configuration|
|declarativeInterfaceReactiveMode|What type of reactive style to use in Spring Http declarative interface|<dl><dt>**coroutines**</dt><dd>Use kotlin-idiomatic 'suspend' functions</dd><dt>**reactor**</dt><dd>Use reactor return wrappers 'Mono' and 'Flux'</dd></dl>|coroutines|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String BEAN_QUALIFIERS = "beanQualifiers";
public static final String USE_RESPONSE_ENTITY = "useResponseEntity";
public static final String DECLARATIVE_INTERFACE_REACTIVE_MODE = "declarativeInterfaceReactiveMode";
public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId";

public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
public static final String USE_SPRING_BOOT4 = "useSpringBoot4";
Expand Down Expand Up @@ -223,6 +224,8 @@ public void setAutoXSpringPaginated(boolean autoXSpringPaginated) {
protected boolean useSpringBoot4 = false;
@Getter @Setter
protected boolean useSpringBuiltInValidation = false;
@Getter @Setter
protected String clientRegistrationId = null;
@Setter
@Getter
protected RequestMappingMode requestMappingMode = RequestMappingMode.controller;
Expand Down Expand Up @@ -426,6 +429,8 @@ public KotlinSpringServerCodegen() {
}
cliOptions.add(declarativeInterfaceReactiveModeOpt);

cliOptions.add(CliOption.newString(CLIENT_REGISTRATION_ID, "Client registration ID for OAuth2 in Spring HTTP Interface (@ClientRegistrationId annotation). Requires library=" + SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY + " and useSpringBoot4=true (Spring Security 7)."));

if (null != defaultDocumentationProvider()) {
CliOption documentationProviderCliOption = new CliOption(DOCUMENTATION_PROVIDER,
"Select the OpenAPI documentation provider.")
Expand Down Expand Up @@ -601,6 +606,9 @@ public void processOpts() {
if (additionalProperties.containsKey(USE_SPRING_BOOT4)) {
this.setUseSpringBoot4(convertPropertyToBoolean(USE_SPRING_BOOT4));
}
if (additionalProperties.containsKey(CLIENT_REGISTRATION_ID)) {
this.setClientRegistrationId(additionalProperties.get(CLIENT_REGISTRATION_ID).toString());
}
if (additionalProperties.containsKey(USE_SPRING_BUILT_IN_VALIDATION)) {
this.setUseSpringBuiltInValidation(convertPropertyToBoolean(USE_SPRING_BUILT_IN_VALIDATION));
writePropertyBack(USE_SPRING_BUILT_IN_VALIDATION, useSpringBuiltInValidation);
Expand Down Expand Up @@ -805,6 +813,17 @@ public void processOpts() {
writePropertyBack(USE_FEIGN_CLIENT, useFeignClient);
writePropertyBack(SKIP_DEFAULT_INTERFACE, skipDefaultInterface);
}
if (clientRegistrationId != null && !clientRegistrationId.isEmpty()) {
if (!SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY.equals(library)) {
throw new IllegalArgumentException(CLIENT_REGISTRATION_ID + " is only supported with the " + SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY + " library");
}
if (!isUseSpringBoot4()) {
throw new IllegalArgumentException(CLIENT_REGISTRATION_ID + " requires " + USE_SPRING_BOOT4 + "=true because @ClientRegistrationId is provided by Spring Security 7");
}
additionalProperties.put(CLIENT_REGISTRATION_ID, clientRegistrationId);
} else {
additionalProperties.remove(CLIENT_REGISTRATION_ID);
}
writePropertyBack(REACTIVE, reactive);
writePropertyBack(REACTIVE_MULTIPART, reactive && SPRING_BOOT.equals(library));
writePropertyBack(EXCEPTION_HANDLER, exceptionHandler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,10 @@ public CodegenModel fromModel(String name, Schema model) {
}
else {
// In-placed type (primitive), because there is no mapping or ref for it.
// use camelized `title` if present, otherwise use `type`
String oneOfName = Optional.ofNullable(schema.getTitle()).orElseGet(schema::getType);
// use camelized `title` if present, otherwise use `type`, otherwise the property's base type
String oneOfName = Optional.ofNullable(schema.getTitle())
.or(() -> Optional.ofNullable(ModelUtils.getType(schema)))
.orElse(oneOf.baseType);
oneOf.setName(toModelName(oneOfName));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import io.swagger.annotations.AuthorizationScope

import org.springframework.web.service.annotation.*
import org.springframework.web.bind.annotation.*
{{#clientRegistrationId}}
import org.springframework.security.oauth2.client.annotation.ClientRegistrationId
{{/clientRegistrationId}}
{{^useResponseEntity}}
import org.springframework.http.HttpStatus
{{/useResponseEntity}}
Expand All @@ -55,6 +58,9 @@ import kotlin.collections.Map
{{#useBeanValidation}}
@Validated
{{/useBeanValidation}}
{{#clientRegistrationId}}
@ClientRegistrationId("{{{clientRegistrationId}}}")
{{/clientRegistrationId}}
{{#operations}}
interface {{classname}} {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ dependencies {
{{/useJackson3}}

{{#hasAuthMethods}}
implementation("org.springframework.boot:spring-boot-starter-oauth2-client"){{/hasAuthMethods}}
implementation("org.springframework.boot:spring-boot-starter-oauth2-client"){{/hasAuthMethods}}{{^hasAuthMethods}}{{#clientRegistrationId}}
implementation("org.springframework.boot:spring-boot-starter-oauth2-client"){{/clientRegistrationId}}{{/hasAuthMethods}}

{{#useBeanValidation}}
implementation("jakarta.validation:jakarta.validation-api"){{/useBeanValidation}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
{{/hasAuthMethods}}
{{^hasAuthMethods}}
{{#clientRegistrationId}}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
{{/clientRegistrationId}}
{{/hasAuthMethods}}
{{#useJackson3}}
<dependency>
<groupId>{{jacksonPackage}}.dataformat</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
{{#fetch-api}}
{{#platforms}}
{{#node}}
"undici": "^7.29.0",
"undici": "^7.29.1",
{{/node}}
{{#browser}}
"whatwg-fetch": "^3.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,67 @@ public void generateHttpInterfaceRendersOperationExtraAnnotation() throws Except
);
}

@Test
public void generateHttpInterfaceWithClientRegistrationId() throws Exception {
Map<String, Object> props = new HashMap<>();
props.put(USE_SPRING_BOOT4, "true");
props.put(CLIENT_REGISTRATION_ID, "my-oauth-client");

Map<String, File> files = generateFromContract("src/test/resources/3_0/kotlin/petstore.yaml", props, new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY));

Path petApi = files.get("PetApi.kt").toPath();
assertFileContains(petApi,
"import org.springframework.security.oauth2.client.annotation.ClientRegistrationId",
"@ClientRegistrationId(\"my-oauth-client\")\ninterface PetApi {");
}

@Test
public void generateHttpInterfaceWithClientRegistrationIdAddsOAuth2ClientDependencyWithoutAuthMethods() throws Exception {
Map<String, Object> props = new HashMap<>();
props.put(USE_SPRING_BOOT4, "true");
props.put(CLIENT_REGISTRATION_ID, "my-oauth-client");

Map<String, File> files = generateFromContract("src/test/resources/3_0/kotlin/bean-qualifiers.yaml", props, new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY));

assertFileContains(files.get("build.gradle.kts").toPath(),
"implementation(\"org.springframework.boot:spring-boot-starter-oauth2-client\")");
}

@Test
public void generateHttpInterfaceWithoutClientRegistrationId() throws Exception {
Map<String, Object> props = new HashMap<>();
props.put(USE_SPRING_BOOT4, "true");

Map<String, File> files = generateFromContract("src/test/resources/3_0/kotlin/petstore.yaml", props, new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY));

assertFileNotContains(files.get("PetApi.kt").toPath(), "ClientRegistrationId");
}

@Test
public void shouldRefuseClientRegistrationIdWithoutSpringBoot4() {
Map<String, Object> props = new HashMap<>();
props.put(CLIENT_REGISTRATION_ID, "my-oauth-client");

Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> generateFromContract("src/test/resources/3_0/kotlin/petstore.yaml", props, new HashMap<>(),
configurator -> configurator.setLibrary(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY)))
.withMessageContaining(USE_SPRING_BOOT4);
}

@Test
public void shouldRefuseClientRegistrationIdOutsideDeclarativeHttpInterface() {
Map<String, Object> props = new HashMap<>();
props.put(USE_SPRING_BOOT4, "true");
props.put(CLIENT_REGISTRATION_ID, "my-oauth-client");

Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> generateFromContract("src/test/resources/3_0/kotlin/petstore.yaml", props))
.withMessageContaining(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY);
}

@Test
public void generateHttpInterfaceReactiveWithCoroutinesResponseEntity() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,20 @@ public void testMapQueryParamsSerializeAsJson() throws IOException {
TestUtils.assertFileNotContains(outputPath, "param_value.to_string()");
}
}

@Test
public void testOneOfMemberWithoutTitleOrType() throws IOException {
Path target = Files.createTempDirectory("test");
target.toFile().deleteOnExit();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("rust")
.setInputSpec("src/test/resources/3_0/rust/oneof-member-without-type.yaml")
.setSkipOverwrite(false)
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
Path outputPath = Path.of(target.toString(), "/src/models/conditions.rs");
TestUtils.assertFileExists(outputPath);
TestUtils.assertFileContains(outputPath, "WorkflowPathCondition(Box<models::WorkflowPathCondition>)");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
openapi: 3.0.3
info:
title: oneOf member without a type
version: "1"
paths:
/conditions:
get:
operationId: getConditions
responses:
"200":
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Conditions"
components:
schemas:
Conditions:
type: object
oneOf:
- type: object
maxProperties: 0
- allOf:
- $ref: "#/components/schemas/WorkflowPathCondition"
required:
- workflow_path
maxProperties: 1
WorkflowPathCondition:
type: object
properties:
workflow_path:
type: string
2 changes: 1 addition & 1 deletion samples/client/echo_api/typescript/build/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading