Skip to content

Commit

Permalink
Add sanitization logic for generated flatten schema names
Browse files Browse the repository at this point in the history
  • Loading branch information
TharmiganK committed Sep 10, 2024
1 parent dd5a48e commit 22dce50
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 8 deletions.
74 changes: 67 additions & 7 deletions openapi-cli/src/main/java/io/ballerina/openapi/cmd/Flatten.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
package io.ballerina.openapi.cmd;

import io.ballerina.cli.BLauncherCmd;
import io.ballerina.openapi.core.generators.common.OASModifier;
import io.ballerina.openapi.core.generators.common.model.Filter;
import io.ballerina.openapi.service.mapper.utils.CodegenUtils;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import io.swagger.v3.parser.util.InlineModelResolver;
Expand All @@ -36,7 +39,10 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

Expand All @@ -45,6 +51,8 @@
import static io.ballerina.openapi.cmd.CmdConstants.YAML_EXTENSION;
import static io.ballerina.openapi.cmd.CmdConstants.YML_EXTENSION;
import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE;
import static io.ballerina.openapi.core.generators.common.OASModifier.getResolvedNameMapping;
import static io.ballerina.openapi.core.generators.common.OASModifier.getValidNameForType;
import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName;

/**
Expand Down Expand Up @@ -79,6 +87,8 @@ public class Flatten implements BLauncherCmd {
private static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:";
private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "[ERROR] error occurred while " +
"writing the flattened OpenAPI definition file";
private static final String ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES = "[ERROR] error occurred while " +
"generating schema names";

private final PrintStream infoStream = System.out;
private final PrintStream errorStream = System.err;
Expand Down Expand Up @@ -176,29 +186,35 @@ private void generateFlattenOpenAPI() {
private Optional<OpenAPI> getFlattenOpenAPI(String openAPIFileContent) {
// Read the contents of the file with default parser options
// Flattening will be done after filtering the operations
SwaggerParseResult parseResult = new OpenAPIParser().readContents(openAPIFileContent, null,
SwaggerParseResult parserResult = new OpenAPIParser().readContents(openAPIFileContent, null,
new ParseOptions());
if (!parseResult.getMessages().isEmpty() &&
parseResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) {
if (!parserResult.getMessages().isEmpty() &&
parserResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) {
errorStream.println(ERROR_UNSUPPORTED_OPENAPI_VERSION);
return Optional.empty();
}

OpenAPI openAPI = parseResult.getOpenAPI();
OpenAPI openAPI = parserResult.getOpenAPI();
if (Objects.isNull(openAPI)) {
errorStream.println(ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE);
if (!parseResult.getMessages().isEmpty()) {
if (!parserResult.getMessages().isEmpty()) {
errorStream.println(FOUND_PARSER_DIAGNOSTICS);
parseResult.getMessages().forEach(errorStream::println);
parserResult.getMessages().forEach(errorStream::println);
}
return Optional.empty();
}

Components components = openAPI.getComponents();
List<String> existingComponentNames = Objects.nonNull(components) && Objects.nonNull(components.getSchemas()) ?
new ArrayList<>(components.getSchemas().keySet()) : new ArrayList<>();

filterOpenAPIOperations(openAPI);

// Flatten the OpenAPI definition with `flattenComposedSchemas: true` and `camelCaseFlattenNaming: true`
InlineModelResolver inlineModelResolver = new InlineModelResolver(true, true);
inlineModelResolver.flatten(openAPI);
return Optional.of(openAPI);

return sanitizeOpenAPI(openAPI, existingComponentNames);
}

private void writeFlattenOpenAPIFile(OpenAPI openAPI) {
Expand Down Expand Up @@ -247,6 +263,50 @@ private void filterOpenAPIOperations(OpenAPI openAPI) {
pathsToRemove.forEach(openAPI.getPaths()::remove);
}

private Optional<OpenAPI> sanitizeOpenAPI(OpenAPI openAPI, List<String> existingComponentNames) {
Map<String, String> proposedNameMapping = getProposedNameMapping(openAPI, existingComponentNames);
if (proposedNameMapping.isEmpty()) {
return Optional.of(openAPI);
}

SwaggerParseResult parserResult = OASModifier.getOASWithSchemaNameModification(openAPI, proposedNameMapping);
openAPI = parserResult.getOpenAPI();
if (Objects.isNull(openAPI)) {
errorStream.println(ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES);
if (!parserResult.getMessages().isEmpty()) {
errorStream.println(FOUND_PARSER_DIAGNOSTICS);
parserResult.getMessages().forEach(errorStream::println);
}
return Optional.empty();
}

return Optional.of(openAPI);
}

public Map<String, String> getProposedNameMapping(OpenAPI openapi, List<String> existingComponentNames) {
Map<String, String> nameMap = new HashMap<>();
if (Objects.isNull(openapi.getComponents())) {
return Collections.emptyMap();
}
Components components = openapi.getComponents();
Map<String, Schema> schemas = components.getSchemas();
if (Objects.isNull(schemas)) {
return Collections.emptyMap();
}

for (Map.Entry<String, Schema> schemaEntry: schemas.entrySet()) {
if (existingComponentNames.contains(schemaEntry.getKey())) {
continue;
}
String modifiedName = getValidNameForType(schemaEntry.getKey());
if (modifiedName.equals(schemaEntry.getKey())) {
continue;
}
nameMap.put(schemaEntry.getKey(), modifiedName);
}
return getResolvedNameMapping(nameMap);
}

private Filter getFilter() {
List<String> tagList = new ArrayList<>();
List<String> operationList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SYNOPSIS

DESCRIPTION
Flatten the OpenAPI contract file by moving all the inline schemas to the
components section.
components section. The generated schema names will be Ballerina friendly.

OPTIONS
-i, --input <openapi-contract-file-path>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,16 @@ public void testFlattenCmdOperations() throws IOException {
compareFiles(expectedFilePath, tmpDir.resolve("flattened_openapi.json"));
}

@Test(description = "Test openapi flatten sub command with composed schema")
public void testFlattenCmdWithComposedSchema() throws IOException {
Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/flatten/flattened_openapi_composed_schema.yaml"));
String[] args = {"-i", resourceDir + "/cmd/flatten/openapi_composed_schema.yaml", "-o", tmpDir.toString()};
Flatten flatten = new Flatten();
new CommandLine(flatten).parseArgs(args);
flatten.execute();
compareFiles(expectedFilePath, tmpDir.resolve("flattened_openapi.yaml"));
}

@AfterTest
public void clean() {
System.setErr(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
openapi: 3.0.1
info:
title: Swagger Petstore
license:
name: MIT
version: 1.0.0
servers:
- url: /
paths:
/pets:
get:
tags:
- pets
summary: List all pets
operationId: listPets
responses:
"200":
description: An paged array of pets
headers:
x-next:
description: A link to the next page of responses
style: simple
explode: false
schema:
type: string
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InlineResponse200"
post:
tags:
- pets
summary: Create a pet
operationId: createPets
requestBody:
description: Created pet object
content:
application/json:
schema:
$ref: "#/components/schemas/PetsBody"
required: true
responses:
"201":
description: Null response
/pets/{petId}:
get:
tags:
- pets
summary: Info for a specific pet
operationId: showPetById
parameters:
- name: petId
in: path
description: The id of the pet to retrieve
required: true
style: simple
explode: false
schema:
type: integer
format: int64
responses:
"200":
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/InlineResponse2001"
/locations:
get:
tags:
- locations
summary: List all locations
operationId: listLocations
parameters:
- name: area
in: query
description: area to filter by
required: false
style: form
explode: true
schema:
type: string
responses:
"200":
description: An paged array of locations
headers:
x-next:
description: A link to the next page of responses
style: simple
explode: false
schema:
type: string
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/PetsOneOf1"
components:
schemas:
PetspetsOneOf12:
type: object
properties:
name:
type: string
tag:
type: string
InlineResponse2001:
oneOf:
- type: object
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
- type: object
properties:
code:
type: integer
message:
type: string
InlineResponse200:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
PetsOneOf1:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
PetsBody:
oneOf:
- $ref: "#/components/schemas/PetsOneOf1"
- $ref: "#/components/schemas/PetspetsOneOf12"
Loading

0 comments on commit 22dce50

Please sign in to comment.