diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java index a4c7dc412..70501b633 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java @@ -54,7 +54,7 @@ public String getDefaultFileName() { @Override public Optional generate(String openAPIFileContent) { Optional filteredOpenAPI = getFilteredOpenAPI(openAPIFileContent); - return filteredOpenAPI.flatMap(this::getFlattenOpenAPI).flatMap(this::sanitizeOpenAPI); + return filteredOpenAPI.flatMap(this::sanitizeOpenAPI); } private Optional sanitizeOpenAPI(OpenAPI openAPI) { diff --git a/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help index d2154a38a..cbd6607e6 100644 --- a/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help +++ b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help @@ -12,10 +12,8 @@ SYNOPSIS DESCRIPTION Sanitize the OpenAPI contract file according to the best naming - practices of Ballerina. The OpenAPI contract is flatten and the type - schema names are made Ballerina friendly. The Ballerina name - extensions are added to the schemas which can not be modified - directly. + practices of Ballerina. The Ballerina name extensions are added + to the schemas which can not be modified directly. OPTIONS diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json index 0f28589f5..205e5954c 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json @@ -1,116 +1,135 @@ { - "openapi": "3.0.1", - "info": { - "title": "Api V1", - "version": "0.0.0" + "openapi" : "3.0.1", + "info" : { + "title" : "Api V1", + "version" : "0.0.0" }, - "servers": [ - { - "url": "http://{server}:{port}/api/v1", - "variables": { - "server": { - "default": "localhost" - }, - "port": { - "default": "8080" - } + "servers" : [ { + "url" : "http://{server}:{port}/api/v1", + "variables" : { + "server" : { + "default" : "localhost" + }, + "port" : { + "default" : "8080" } } - ], - "paths": { - "/albums": { - "get": { - "tags": [ - "albums" - ], - "operationId": "getAlbums", - "parameters": [ - { - "name": "_artists_", - "in": "query", - "required": false, - "style": "form", - "explode": true, - "schema": { - "type": "array", - "items": { - "type": "string" - }, - "default": [] + } ], + "paths" : { + "/albums" : { + "get" : { + "tags" : [ "albums" ], + "operationId" : "getAlbums", + "parameters" : [ { + "name" : "_artists_", + "in" : "query", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" }, - "x-ballerina-name": "artists" + "default" : [ ] }, - { - "name": "X-API-VERSION", - "in": "header", - "required": false, - "style": "simple", - "explode": false, - "schema": { - "type": "string", - "default": "v1" - }, - "x-ballerina-name": "xAPIVERSION" - } - ], - "responses": { - "200": { - "description": "Ok", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Album" + "x-ballerina-name" : "artists" + }, { + "name" : "X-API-VERSION", + "in" : "header", + "required" : false, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string", + "default" : "v1" + }, + "x-ballerina-name" : "xAPIVERSION" + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Album" } } } } }, - "400": { - "description": "BadRequest", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorPayload" + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" } } } } } }, - "post": { - "tags": [ - "albums" - ], - "operationId": "postAlbum", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AlbumsBody" + "post" : { + "tags" : [ "albums" ], + "operationId" : "postAlbum", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "oneOf" : [ { + "required" : [ "_id", "artist", "title" ], + "type" : "object", + "properties" : { + "artist" : { + "type" : "string" + }, + "_id" : { + "type" : "string", + "x-ballerina-name" : "id" + }, + "title" : { + "type" : "string" + } + }, + "additionalProperties" : false + }, { + "required" : [ "artist", "title" ], + "type" : "object", + "properties" : { + "artist" : { + "type" : "string" + }, + "title" : { + "type" : "string" + } + }, + "additionalProperties" : false + } ] } } }, - "required": true + "required" : true }, - "responses": { - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Album" + "responses" : { + "201" : { + "description" : "Created", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Album" } } } }, - "400": { - "description": "BadRequest", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorPayload" + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" } } } @@ -118,51 +137,47 @@ } } }, - "/albums/{id}": { - "get": { - "tags": [ - "albums" - ], - "operationId": "getAlbumById", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } + "/albums/{id}" : { + "get" : { + "tags" : [ "albums" ], + "operationId" : "getAlbumById", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" } - ], - "responses": { - "200": { - "description": "Ok", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Album" + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Album" } } } }, - "400": { - "description": "BadRequest", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorPayload" + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" } } } }, - "404": { - "description": "NotFound", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Message" + "404" : { + "description" : "NotFound", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Message" } } } @@ -170,41 +185,53 @@ } } }, - "/albums/{id}/artist": { - "get": { - "tags": [ - "artists" - ], - "operationId": "getArtistByAlbum", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } + "/albums/{id}/artist" : { + "get" : { + "tags" : [ "artists" ], + "operationId" : "getArtistByAlbum", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" } - ], - "responses": { - "200": { - "description": "Ok", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InlineResponse200" + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "required" : [ "albums", "id", "name" ], + "type" : "object", + "properties" : { + "albums" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Album" + } + }, + "name" : { + "type" : "string" + }, + "id" : { + "type" : "string" + } + }, + "additionalProperties" : false } } } }, - "400": { - "description": "BadRequest", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorPayload" + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" } } } @@ -213,147 +240,63 @@ } } }, - "components": { - "schemas": { - "ErrorPayload": { - "required": [ - "message", - "method", - "path", - "reason", - "status", - "timestamp" - ], - "type": "object", - "properties": { - "reason": { - "type": "string" + "components" : { + "schemas" : { + "ErrorPayload" : { + "required" : [ "message", "method", "path", "reason", "status", "timestamp" ], + "type" : "object", + "properties" : { + "reason" : { + "type" : "string" }, - "path": { - "type": "string" + "path" : { + "type" : "string" }, - "method": { - "type": "string" + "method" : { + "type" : "string" }, - "message": { - "type": "string" + "message" : { + "type" : "string" }, - "timestamp": { - "type": "string" + "timestamp" : { + "type" : "string" }, - "status": { - "type": "integer", - "format": "int64" + "status" : { + "type" : "integer", + "format" : "int64" } } }, - "AlbumsBody": { - "oneOf": [ - { - "$ref": "#/components/schemas/AlbumsOneOf1" - }, - { - "$ref": "#/components/schemas/AlbumsalbumsOneOf12" - } - ] - }, - "Message": { - "required": [ - "code", - "message" - ], - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int64" - }, - "message": { - "type": "string" - } - }, - "additionalProperties": false - }, - "AlbumsalbumsOneOf12": { - "required": [ - "artist", - "title" - ], - "type": "object", - "properties": { - "artist": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Album": { - "required": [ - "_id", - "artist", - "title" - ], - "type": "object", - "properties": { - "artist": { - "type": "string" - }, - "_id": { - "type": "string", - "x-ballerina-name": "id" - }, - "title": { - "type": "string" - } - }, - "additionalProperties": false - }, - "InlineResponse200": { - "required": [ - "albums", - "id", - "name" - ], - "type": "object", - "properties": { - "albums": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Album" - } - }, - "name": { - "type": "string" + "Message" : { + "required" : [ "code", "message" ], + "type" : "object", + "properties" : { + "code" : { + "type" : "integer", + "format" : "int64" }, - "id": { - "type": "string" + "message" : { + "type" : "string" } }, - "additionalProperties": false + "additionalProperties" : false }, - "AlbumsOneOf1": { - "required": [ - "_id", - "artist", - "title" - ], - "type": "object", - "properties": { - "artist": { - "type": "string" + "Album" : { + "required" : [ "_id", "artist", "title" ], + "type" : "object", + "properties" : { + "artist" : { + "type" : "string" }, - "_id": { - "type": "string", - "x-ballerina-name": "id" + "_id" : { + "type" : "string", + "x-ballerina-name" : "id" }, - "title": { - "type": "string" + "title" : { + "type" : "string" } }, - "additionalProperties": false + "additionalProperties" : false } } } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java index cfb8cf831..677686bcb 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java @@ -32,6 +32,7 @@ import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; @@ -114,9 +115,111 @@ public OpenAPI modifyWithBallerinaConventions(OpenAPI openapi, Map processPathItem(pathItem)); + } + + private void processPathItem(PathItem pathItem) { + pathItem.readOperationsMap().forEach((method, operation) -> processOperationWithInlineObjectSchema(operation)); + } + + private void processOperationWithInlineObjectSchema(Operation operation) { + if (Objects.nonNull(operation.getRequestBody())) { + updateInlineObjectInContent(operation.getRequestBody().getContent()); + } + processParametersWithInlineObjectSchema(operation.getParameters()); + processResponsesWithInlineObjectSchema(operation.getResponses()); + } + + private void processParametersWithInlineObjectSchema(List parameters) { + if (Objects.isNull(parameters)) { + return; + } + + for (Parameter parameter : parameters) { + if (Objects.nonNull(parameter.getSchema())) { + updateInlineObjectSchema(parameter.getSchema()); + } + } + } + + private void processResponsesWithInlineObjectSchema(Map responses) { + responses.forEach((status, response) -> { + if (Objects.nonNull(response.getContent())) { + updateInlineObjectInContent(response.getContent()); + } + }); + } + + private void updateInlineObjectSchema(Schema schema) { + if (Objects.isNull(schema)) { + return; + } + handleInlineObjectSchemaInComposedSchema(schema); + handleInlineObjectSchema(schema); + handleInlineObjectSchemaItems(schema); + handleInlineObjectSchemaInAdditionalProperties(schema); + handleInlineObjectSchemaProperties(schema); + } + + private void handleInlineObjectSchemaInComposedSchema(Schema schema) { + if (schema instanceof ComposedSchema composedSchema) { + processSubSchemas(composedSchema.getAllOf()); + processSubSchemas(composedSchema.getAnyOf()); + processSubSchemas(composedSchema.getOneOf()); + } + } + + private void processSubSchemas(List subSchemas) { + if (Objects.nonNull(subSchemas)) { + subSchemas.forEach(this::updateInlineObjectSchema); + } + } + + private static void handleInlineObjectSchema(Schema schema) { + if (isInlineObjectSchema(schema)) { + Map properties = schema.getProperties(); + if (Objects.nonNull(properties) && !properties.isEmpty()) { + schema.setProperties(getPropertiesWithBallerinaNameExtension(properties)); + } + } + } + + private static boolean isInlineObjectSchema(Schema schema) { + return schema instanceof ObjectSchema || + (Objects.isNull(schema.getType()) && Objects.isNull(schema.get$ref()) && + Objects.nonNull(schema.getProperties())); + } + + private void handleInlineObjectSchemaItems(Schema schema) { + if (Objects.nonNull(schema.getItems())) { + updateInlineObjectSchema(schema.getItems()); + } + } + + private void handleInlineObjectSchemaInAdditionalProperties(Schema schema) { + if (schema.getAdditionalProperties() instanceof Schema) { + updateInlineObjectSchema((Schema) schema.getAdditionalProperties()); + } + } + + private void handleInlineObjectSchemaProperties(Schema schema) { + if (Objects.nonNull(schema.getProperties())) { + schema.getProperties().values().forEach(this::updateInlineObjectSchema); + } + } + + private void updateInlineObjectInContent(Map content) { + for (Map.Entry entry : content.entrySet()) { + Schema schema = entry.getValue().getSchema(); + updateInlineObjectSchema(schema); + } + } + private static void modifyOASWithObjectPropertyName(OpenAPI openapi) { Components components = openapi.getComponents(); if (Objects.isNull(components) || Objects.isNull(components.getSchemas())) { @@ -338,7 +441,7 @@ private static Schema getSchemaWithBallerinaNameExtension(String propertyName, S } if (Objects.nonNull(propertySchema.get$ref())) { - Schema refSchema = new Schema<>(); + Schema refSchema = new Schema<>(); refSchema.set$ref(propertySchema.get$ref()); propertySchema.set$ref(null); propertySchema.addAllOfItem(refSchema);