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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sap.cloud.sdk.datamodel.openapi.generator;

import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_RESPONSE_SCHEMA_TITLES;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Year;
Expand All @@ -19,7 +21,11 @@
import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;

import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.core.models.AuthorizationValue;
import io.swagger.v3.parser.core.models.ParseOptions;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -60,9 +66,11 @@ static ClientOptInput convertGenerationConfiguration(
config.setTemplateDir(TEMPLATE_DIRECTORY);
config.additionalProperties().putAll(getAdditionalProperties(generationConfiguration));

final var openAPI = parseOpenApiSpec(inputSpecFile, generationConfiguration);

final var clientOptInput = new ClientOptInput();
clientOptInput.config(config);
clientOptInput.openAPI(parseOpenApiSpec(inputSpecFile));
clientOptInput.openAPI(openAPI);
return clientOptInput;
}

Expand Down Expand Up @@ -90,16 +98,60 @@ private static void setGlobalSettings( @Nonnull final GenerationConfiguration co
GlobalSettings.setProperty(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString());
}

private static OpenAPI parseOpenApiSpec( @Nonnull final String inputSpecFile )
@Nonnull
private static
OpenAPI
parseOpenApiSpec( @Nonnull final String inputSpecFile, @Nonnull final GenerationConfiguration config )
{
final List<AuthorizationValue> authorizationValues = List.of();
final var authorizationValues = List.<AuthorizationValue> of();
final var options = new ParseOptions();
options.setResolve(true);
final var spec = new OpenAPIParser().readLocation(inputSpecFile, authorizationValues, options);
if( !spec.getMessages().isEmpty() ) {
log.warn("Parsing the specification yielded the following messages: {}", spec.getMessages());
}
return spec.getOpenAPI();
final var result = spec.getOpenAPI();
preprocessSpecification(result, config);
return result;
}

/**
* Preprocesses the OpenAPI specification to ensure that all inline schemas in "//components/responses" have a
* title. This does not affect regular schema definitions in "//components/schemas"! Without this fix, the OpenAPI
* Generator will generate classes with name format "InlineObject\d*" with high chance of naming conflicts.
*
* @param openAPI
* the OpenAPI specification to preprocess
* @param config
* the generation configuration to extract feature toggles from
*/
private static
void
preprocessSpecification( @Nonnull final OpenAPI openAPI, @Nonnull final GenerationConfiguration config )
{
if( !FIX_RESPONSE_SCHEMA_TITLES.isEnabled(config) ) {
return;
}
final Components components = openAPI.getComponents();
if( components == null ) {
return;
}
final Map<String, ApiResponse> responses = components.getResponses();
if( responses == null ) {
return;
}
responses.forEach(( key, value ) -> {
final Content mediaContent = value.getContent();
if( mediaContent == null ) {
return;
}
mediaContent.forEach(( mediaType, content ) -> {
final Schema<?> schema = content.getSchema();
if( schema != null && schema.getTitle() == null ) {
schema.setTitle(key + " " + (mediaContent.size() > 1 ? mediaType : ""));
}
});
});
}

private static Map<String, Object> getAdditionalProperties( @Nonnull final GenerationConfiguration config )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ enum GeneratorCustomProperties
/**
* Remove schema components that are unused, before generating them.
*/
FIX_REMOVE_UNUSED_COMPONENTS("removeUnusedComponents", "false");
FIX_REMOVE_UNUSED_COMPONENTS("removeUnusedComponents", "false"),

/**
* Fix inconsistent "InlineObject\d*" class names for unnamed inline schemas of `//components/responses`.
*/
FIX_RESPONSE_SCHEMA_TITLES("fixResponseSchemaTitles", "false");

private final String key;
private final String defaultValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ private enum TestCase
true,
6,
Map.of()),
INLINEOBJECT_SCHEMA_NAME(
"inlineobject-schemas-enabled",
"sodastore.yaml",
"com.sap.cloud.sdk.services.inlineobject.api",
"com.sap.cloud.sdk.services.inlineobject.model",
ApiMaturity.RELEASED,
true,
true,
5,
Map.of("fixResponseSchemaTitles", "true")),
PARTIAL_GENERATION(
"partial-generation",
"sodastore.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
openapi: 3.0.0
info:
title: Soda Store API
version: 1.0.0
description: API for managing sodas in a soda store

paths:
/sodas/{sodaId}:
get:
summary: Get details of a specific soda
operationId: getSodaById
parameters:
- name: sodaId
in: path
description: ID of the soda to retrieve
required: true
schema:
type: integer
format: int64
responses:
'200':
description: The requested soda
content:
application/json:
schema:
$ref: '#/components/schemas/Soda'
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/ServiceUnavailable'

components:
schemas:
Soda:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
brand:
type: string
flavor:
type: string
price:
type: number
format: float
required:
- name
- brand
- flavor
- price
responses:
NotFound:
description: The specified resource was not found
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Resource not found
ServiceUnavailable:
description: The service is currently unavailable
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Resource not found
application/xml:
schema:
type: object
properties:
message:
type: string
example: Resource not found
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved.
*/

package com.sap.cloud.sdk.services.inlineobject.api;

import com.sap.cloud.sdk.services.openapi.core.OpenApiRequestException;
import com.sap.cloud.sdk.services.openapi.core.OpenApiResponse;
import com.sap.cloud.sdk.services.openapi.core.AbstractOpenApiService;
import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient;

import com.sap.cloud.sdk.services.inlineobject.model.NotFound;
import com.sap.cloud.sdk.services.inlineobject.model.ServiceUnavailableApplicationJson;
import com.sap.cloud.sdk.services.inlineobject.model.ServiceUnavailableApplicationXml;
import com.sap.cloud.sdk.services.inlineobject.model.Soda;

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

import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.annotations.Beta;

import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;

/**
* Soda Store API in version 1.0.0.
*
* API for managing sodas in a soda store
*/
public class DefaultApi extends AbstractOpenApiService {
/**
* Instantiates this API class to invoke operations on the Soda Store API.
*
* @param httpDestination The destination that API should be used with
*/
public DefaultApi( @Nonnull final Destination httpDestination )
{
super(httpDestination);
}

/**
* Instantiates this API class to invoke operations on the Soda Store API based on a given {@link ApiClient}.
*
* @param apiClient
* ApiClient to invoke the API on
*/
@Beta
public DefaultApi( @Nonnull final ApiClient apiClient )
{
super(apiClient);
}

/**
* <p>Get details of a specific soda</p>
* <p></p>
* <p><b>200</b> - The requested soda
* <p><b>404</b> - The specified resource was not found
* <p><b>503</b> - The service is currently unavailable
* @param sodaId
* ID of the soda to retrieve
* @return Soda
* @throws OpenApiRequestException if an error occurs while attempting to invoke the API
*/
@Nonnull
public Soda getSodaById( @Nonnull final Long sodaId) throws OpenApiRequestException {
final Object localVarPostBody = null;

// verify the required parameter 'sodaId' is set
if (sodaId == null) {
throw new OpenApiRequestException("Missing the required parameter 'sodaId' when calling getSodaById");
}

// create path and map variables
final Map<String, Object> localVarPathParams = new HashMap<String, Object>();
localVarPathParams.put("sodaId", sodaId);
final String localVarPath = UriComponentsBuilder.fromPath("/sodas/{sodaId}").buildAndExpand(localVarPathParams).toUriString();

final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
final HttpHeaders localVarHeaderParams = new HttpHeaders();
final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();

final String[] localVarAccepts = {
"application/json", "application/xml"
};
final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
final String[] localVarContentTypes = { };
final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

final String[] localVarAuthNames = new String[] { };

final ParameterizedTypeReference<Soda> localVarReturnType = new ParameterizedTypeReference<Soda>() {};
return apiClient.invokeAPI(localVarPath, HttpMethod.GET, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
}
Loading
Loading