Skip to content
Open
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', 'original', and 'bestEffortBacktick' (like 'original' but tries to wrap values in backticks before falling back to sanitizing, e.g. `name,asc` stays `name,asc` rather than becoming nameCommaAsc; useful for sort/order enums)| |original|
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response. With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.| |false|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
|generateGenericResponseEntity|Select when to use ResponseEntity&lt;*&gt; for generated API methods. Ignored when useResponseEntity=false.|<dl><dt>**NEVER**</dt><dd>Keep the declared response body type.</dd><dt>**ALWAYS**</dt><dd>Use ResponseEntity&lt;*&gt; for all generated API methods.</dd></dl>|NEVER|
|generateJsonIncludeAnnotations|Whether to generate policy @JsonInclude annotations on model properties. When true, emits spec-honest annotations (required-field protection and the optional non-nullable policy from optionalNonNullPropertyJsonInclude). When false, none are generated and the global ObjectMapper owns inclusion. When left unset it defaults to false (7.23.0-equivalent output) and logs a warning; set it explicitly to silence the warning. A per-property override set via the `x-jackson-json-include-policy` vendor extension is always honored regardless of this flag.| |false|
|generateJsonSetterNullsAnnotations|Whether to generate @JsonSetter(nulls = ...) annotations on optional non-nullable model properties. When true, emits @JsonSetter (Nulls.FAIL when openApiNullable is true, otherwise Nulls.SKIP) so an explicit null in the payload is handled explicitly. When false, none are generated and deserialization null-handling defers to the global ObjectMapper. When left unset it defaults to false (7.23.0-equivalent output) and logs a warning; set it explicitly to silence the warning.| |false|
|generatePageableConstraintValidation|Generate a @ValidPageable annotation and PageableConstraintValidator class, and apply @ValidPageable to the injected Pageable parameter of operations whose 'page' or 'size' parameter specifies a maximum constraint. The annotation enforces those constraints on the Pageable object that replaces the individual page/size query parameters. Requires useBeanValidation=true and library is spring-boot or spring-cloud.| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String USE_TAGS = "useTags";
public static final String BEAN_QUALIFIERS = "beanQualifiers";
public static final String USE_RESPONSE_ENTITY = "useResponseEntity";
public static final String GENERATE_GENERIC_RESPONSE_ENTITY = "generateGenericResponseEntity";
public static final String DECLARATIVE_INTERFACE_REACTIVE_MODE = "declarativeInterfaceReactiveMode";
public static final String CLIENT_REGISTRATION_ID = "clientRegistrationId";

Expand All @@ -120,6 +121,18 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String COMPANION_OBJECT = "companionObject";
public static final String SUSPEND_FUNCTIONS = "suspendFunctions";

@Getter
public enum GenericResponseEntityMode {
NEVER("Keep the declared response body type."),
ALWAYS("Use ResponseEntity<*> for all generated API methods.");

private final String description;

GenericResponseEntityMode(String description) {
this.description = description;
}
}

@Getter
public enum DeclarativeInterfaceReactiveMode {
coroutines("Use kotlin-idiomatic 'suspend' functions", "reactiveModeCoroutines"),
Expand Down Expand Up @@ -178,6 +191,7 @@ public enum RequestMappingMode {
@Setter private boolean beanQualifiers = false;
@Setter private DeclarativeInterfaceReactiveMode declarativeInterfaceReactiveMode = DeclarativeInterfaceReactiveMode.coroutines;
@Setter private boolean useResponseEntity = true;
@Setter private GenericResponseEntityMode generateGenericResponseEntity = GenericResponseEntityMode.NEVER;
@Getter private String autoXSpringPaginated = SpringPageableScanUtils.AUTO_PAGINATION_MODE_NONE;
@Getter private SpringPageableScanUtils.AutoPaginationMode autoXSpringPaginatedMode = SpringPageableScanUtils.AutoPaginationMode.NONE;
/**
Expand Down Expand Up @@ -332,6 +346,13 @@ public KotlinSpringServerCodegen() {
addSwitch(USE_RESPONSE_ENTITY,
"Whether (when false) to return actual type (e.g. List<Fruit>) and handle non-happy path responses via exceptions flow or (when true) return entire ResponseEntity (e.g. ResponseEntity<List<Fruit>>). If disabled, method are annotated using a @ResponseStatus annotation, which has the status of the first response declared in the Api definition",
useResponseEntity);
CliOption genericResponseEntityOpt = CliOption.newString(GENERATE_GENERIC_RESPONSE_ENTITY,
"Select when to use ResponseEntity<*> for generated API methods. Ignored when useResponseEntity=false.");
for (GenericResponseEntityMode mode : GenericResponseEntityMode.values()) {
genericResponseEntityOpt.addEnum(mode.name(), mode.getDescription());
}
genericResponseEntityOpt.setDefault(generateGenericResponseEntity.name());
cliOptions.add(genericResponseEntityOpt);
addSwitch(USE_SEALED_RESPONSE_INTERFACES,
"Generate sealed interfaces for endpoint responses that all possible response types implement. Allows controllers to return any valid response type in a type-safe manner (e.g., sealed interface CreateUserResponse implemented by User, ConflictResponse, ErrorResponse)",
useSealedResponseInterfaces);
Expand Down Expand Up @@ -662,11 +683,23 @@ public void processOpts() {
additionalProperties.put(CodegenConstants.LIBRARY, library);
}

if(additionalProperties.containsKey(USE_RESPONSE_ENTITY)) {
if (additionalProperties.containsKey(USE_RESPONSE_ENTITY)) {
this.setUseResponseEntity(Boolean.parseBoolean(additionalProperties.get(USE_RESPONSE_ENTITY).toString()));
}
writePropertyBack(USE_RESPONSE_ENTITY, useResponseEntity);

if (additionalProperties.containsKey(GENERATE_GENERIC_RESPONSE_ENTITY)) {
try {
this.setGenerateGenericResponseEntity(GenericResponseEntityMode.valueOf(
String.valueOf(additionalProperties.get(GENERATE_GENERIC_RESPONSE_ENTITY))));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid value for additional property '" + GENERATE_GENERIC_RESPONSE_ENTITY
+ "'. Supported values are " + Arrays.toString(GenericResponseEntityMode.values()) + ".", e);
}
}
writePropertyBack(GENERATE_GENERIC_RESPONSE_ENTITY, generateGenericResponseEntity.name());
writePropertyBack("useGenericResponseEntity", useResponseEntity && generateGenericResponseEntity == GenericResponseEntityMode.ALWAYS);

if(additionalProperties.containsKey(USE_SEALED_RESPONSE_INTERFACES)) {
this.setUseSealedResponseInterfaces(Boolean.parseBoolean(additionalProperties.get(USE_SEALED_RESPONSE_INTERFACES).toString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class {{classname}}Controller({{#serviceInterface}}@Autowired(required = true) v
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} {
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{#useGenericResponseEntity}}*{{/useGenericResponseEntity}}{{^useGenericResponseEntity}}{{>returnTypes}}{{/useGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{>returnTypes}}{{/useResponseEntity}} {
return {{>returnValue}}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface {{classname}}Delegate {
{{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}},
{{/hasParams}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
{{/includeHttpRequestContext}}{{/hasParams}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
{{/includeHttpRequestContext}}{{/hasParams}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useGenericResponseEntity}}*{{/useGenericResponseEntity}}{{^useGenericResponseEntity}}{{>returnTypesBody}}{{/useGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultDelegateInterface}} {
{{>methodBody}}{{! prevent indent}}
}{{/skipDefaultDelegateInterface}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ interface {{classname}} {
{{/hasParams}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}{{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}},
{{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}},
{{/includeHttpRequestContext}}{{/hasParams}}{{#vendorExtensions.x-pageable-extra-annotation}}{{{.}}} {{/vendorExtensions.x-pageable-extra-annotation}}{{#swagger1AnnotationLibrary}}@ApiParam(hidden = true) {{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}@Parameter(hidden = true) {{/swagger2AnnotationLibrary}}pageable: Pageable{{/vendorExtensions.x-spring-paginated}}{{#hasParams}}
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useSealedResponseInterfaces}}{{#vendorExtensions.x-sealed-response-interface}}{{vendorExtensions.x-sealed-response-interface}}{{/vendorExtensions.x-sealed-response-interface}}{{^vendorExtensions.x-sealed-response-interface}}{{>returnTypes}}{{/vendorExtensions.x-sealed-response-interface}}{{/useSealedResponseInterfaces}}{{^useSealedResponseInterfaces}}{{>returnTypes}}{{/useSealedResponseInterfaces}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultApiInterface}} {
{{/hasParams}}): {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{#useGenericResponseEntity}}*{{/useGenericResponseEntity}}{{^useGenericResponseEntity}}{{>returnTypesBody}}{{/useGenericResponseEntity}}{{#useResponseEntity}}>{{/useResponseEntity}}{{^skipDefaultApiInterface}} {
{{^isDelegate}}
return {{>returnValue}}
{{/isDelegate}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class {{classname}}Test {
{{/allParams}}
{{#includeHttpRequestContext}}val {{#reactive}}exchange: org.springframework.web.server.ServerWebExchange{{/reactive}}{{^reactive}}request: {{javaxPackage}}.servlet.http.HttpServletRequest{{/reactive}} = TODO(){{/includeHttpRequestContext}}
{{#vendorExtensions.x-spring-paginated}}val pageable: Pageable = TODO(){{/vendorExtensions.x-spring-paginated}}
val response: {{#useResponseEntity}}ResponseEntity<{{/useResponseEntity}}{{>returnTypes}}{{#useResponseEntity}}>{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})
val response: {{#useResponseEntity}}ResponseEntity<{{#useGenericResponseEntity}}*{{/useGenericResponseEntity}}{{^useGenericResponseEntity}}{{>returnTypes}}{{/useGenericResponseEntity}}>{{/useResponseEntity}}{{^useResponseEntity}}{{>returnTypes}}{{/useResponseEntity}} = api.{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}{{#includeHttpRequestContext}}{{#hasParams}}, {{/hasParams}}{{#reactive}}exchange{{/reactive}}{{^reactive}}request{{/reactive}}{{/includeHttpRequestContext}}{{#vendorExtensions.x-spring-paginated}}{{#hasParams}}, {{/hasParams}}{{^hasParams}}{{#includeHttpRequestContext}}, {{/includeHttpRequestContext}}{{/hasParams}}pageable{{/vendorExtensions.x-spring-paginated}})

// TODO: test validations
}
Expand Down
Loading
Loading