From 44324cba9154017132584c5749af4bdc0f897091 Mon Sep 17 00:00:00 2001 From: aneeshafedo Date: Wed, 19 Jul 2023 14:06:42 +0530 Subject: [PATCH 01/16] Add error for 3.1 openapis --- .../ballerina/openapi/cmd/BallerinaCodeGenerator.java | 11 ++++++++++- .../java/io/ballerina/openapi/cmd/CmdConstants.java | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java index 61c195de5..801ee9a37 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java @@ -62,9 +62,11 @@ import static io.ballerina.openapi.cmd.CmdConstants.GenType.GEN_CLIENT; import static io.ballerina.openapi.cmd.CmdConstants.GenType.GEN_SERVICE; import static io.ballerina.openapi.cmd.CmdConstants.OAS_PATH_SEPARATOR; +import static io.ballerina.openapi.cmd.CmdConstants.OPENAPI_3_1_VERSION; import static io.ballerina.openapi.cmd.CmdConstants.TEST_DIR; import static io.ballerina.openapi.cmd.CmdConstants.TEST_FILE_NAME; import static io.ballerina.openapi.cmd.CmdConstants.TYPE_FILE_NAME; +import static io.ballerina.openapi.cmd.CmdConstants.UNSUPPORTED_OAS_3_1_ERROR; import static io.ballerina.openapi.cmd.CmdConstants.UNTITLED_SERVICE; import static io.ballerina.openapi.cmd.CmdConstants.UTIL_FILE_NAME; import static io.ballerina.openapi.cmd.CmdUtils.setGeneratedFileName; @@ -103,7 +105,9 @@ public void generateClientAndService(String definitionPath, String serviceName, // absence of the operationId in operation. Therefor we enable client flag true as default code generation. // if resource is enabled, we avoid checking operationId. OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPIPath, !isResource); - + if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { + throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); + } // Generate service String concatTitle = serviceName.toLowerCase(Locale.ENGLISH); String srcFile = concatTitle + "_service.bal"; @@ -338,6 +342,9 @@ private List generateClientFiles(Path openAPI, Filter filter, boolea List sourceFiles = new ArrayList<>(); // Normalize OpenAPI definition OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, !isResource); + if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { + throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); + } // Generate ballerina service and resources. OASClientConfig.Builder clientMetaDataBuilder = new OASClientConfig.Builder(); OASClientConfig oasClientConfig = clientMetaDataBuilder @@ -407,6 +414,8 @@ public List generateBallerinaService(Path openAPI, String serviceNam if (openAPIDef.getInfo() == null) { throw new BallerinaOpenApiException("Info section of the definition file cannot be empty/null: " + openAPI); + } else if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { + throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); } if (openAPIDef.getInfo().getTitle().isBlank() && (serviceName == null || serviceName.isBlank())) { diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java index d3bdd4224..1f0f01bdb 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java @@ -201,5 +201,8 @@ public String getValue() { // OS specific line separator public static final String LINE_SEPARATOR = System.lineSeparator(); public static final String DOUBLE_LINE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR; + public static final String OPENAPI_3_1_VERSION = "V31"; + public static final String UNSUPPORTED_OAS_3_1_ERROR = + "OpenAPI definitions with version 3.1.x are currently unsupported"; } From fbcedad912570147bd3d52fb0eab8be6d370ec7a Mon Sep 17 00:00:00 2001 From: aneeshafedo Date: Tue, 29 Aug 2023 13:52:40 +0530 Subject: [PATCH 02/16] Add warning message --- .../openapi/cmd/BallerinaCodeGenerator.java | 20 +++++++++++-------- .../ballerina/openapi/cmd/CmdConstants.java | 7 +++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java index 801ee9a37..6015075fe 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java @@ -62,11 +62,10 @@ import static io.ballerina.openapi.cmd.CmdConstants.GenType.GEN_CLIENT; import static io.ballerina.openapi.cmd.CmdConstants.GenType.GEN_SERVICE; import static io.ballerina.openapi.cmd.CmdConstants.OAS_PATH_SEPARATOR; -import static io.ballerina.openapi.cmd.CmdConstants.OPENAPI_3_1_VERSION; +import static io.ballerina.openapi.cmd.CmdConstants.SUPPORTED_OPENAPI_VERSIONS; import static io.ballerina.openapi.cmd.CmdConstants.TEST_DIR; import static io.ballerina.openapi.cmd.CmdConstants.TEST_FILE_NAME; import static io.ballerina.openapi.cmd.CmdConstants.TYPE_FILE_NAME; -import static io.ballerina.openapi.cmd.CmdConstants.UNSUPPORTED_OAS_3_1_ERROR; import static io.ballerina.openapi.cmd.CmdConstants.UNTITLED_SERVICE; import static io.ballerina.openapi.cmd.CmdConstants.UTIL_FILE_NAME; import static io.ballerina.openapi.cmd.CmdUtils.setGeneratedFileName; @@ -105,8 +104,9 @@ public void generateClientAndService(String definitionPath, String serviceName, // absence of the operationId in operation. Therefor we enable client flag true as default code generation. // if resource is enabled, we avoid checking operationId. OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPIPath, !isResource); - if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { - throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); + if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { + outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + + "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); } // Generate service String concatTitle = serviceName.toLowerCase(Locale.ENGLISH); @@ -342,8 +342,9 @@ private List generateClientFiles(Path openAPI, Filter filter, boolea List sourceFiles = new ArrayList<>(); // Normalize OpenAPI definition OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, !isResource); - if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { - throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); + if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { + outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + + "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); } // Generate ballerina service and resources. OASClientConfig.Builder clientMetaDataBuilder = new OASClientConfig.Builder(); @@ -414,8 +415,11 @@ public List generateBallerinaService(Path openAPI, String serviceNam if (openAPIDef.getInfo() == null) { throw new BallerinaOpenApiException("Info section of the definition file cannot be empty/null: " + openAPI); - } else if (openAPIDef.getSpecVersion().name().equals(OPENAPI_3_1_VERSION)) { - throw new BallerinaOpenApiException(UNSUPPORTED_OAS_3_1_ERROR); + } + + if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { + outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + + "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); } if (openAPIDef.getInfo().getTitle().isBlank() && (serviceName == null || serviceName.isBlank())) { diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java index 1f0f01bdb..0bc09af59 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -201,8 +202,6 @@ public String getValue() { // OS specific line separator public static final String LINE_SEPARATOR = System.lineSeparator(); public static final String DOUBLE_LINE_SEPARATOR = LINE_SEPARATOR + LINE_SEPARATOR; - public static final String OPENAPI_3_1_VERSION = "V31"; - public static final String UNSUPPORTED_OAS_3_1_ERROR = - "OpenAPI definitions with version 3.1.x are currently unsupported"; - + public static final List SUPPORTED_OPENAPI_VERSIONS = + List.of("2.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0"); } From f29a7467f13120ef141091dfd0974559430b7433 Mon Sep 17 00:00:00 2001 From: aneeshafedo Date: Tue, 29 Aug 2023 14:22:22 +0530 Subject: [PATCH 03/16] Address review comments --- .../openapi/cmd/BallerinaCodeGenerator.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java index 6015075fe..32f60ecc2 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java @@ -104,10 +104,7 @@ public void generateClientAndService(String definitionPath, String serviceName, // absence of the operationId in operation. Therefor we enable client flag true as default code generation. // if resource is enabled, we avoid checking operationId. OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPIPath, !isResource); - if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { - outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + - "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); - } + checkOpenAPIVersion(openAPIDef); // Generate service String concatTitle = serviceName.toLowerCase(Locale.ENGLISH); String srcFile = concatTitle + "_service.bal"; @@ -342,10 +339,7 @@ private List generateClientFiles(Path openAPI, Filter filter, boolea List sourceFiles = new ArrayList<>(); // Normalize OpenAPI definition OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, !isResource); - if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { - outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + - "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); - } + checkOpenAPIVersion(openAPIDef); // Generate ballerina service and resources. OASClientConfig.Builder clientMetaDataBuilder = new OASClientConfig.Builder(); OASClientConfig oasClientConfig = clientMetaDataBuilder @@ -417,10 +411,7 @@ public List generateBallerinaService(Path openAPI, String serviceNam openAPI); } - if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { - outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + - "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); - } + checkOpenAPIVersion(openAPIDef); if (openAPIDef.getInfo().getTitle().isBlank() && (serviceName == null || serviceName.isBlank())) { openAPIDef.getInfo().setTitle(UNTITLED_SERVICE); @@ -482,4 +473,11 @@ public void setLicenseHeader(String licenseHeader) { public void setIncludeTestFiles(boolean includeTestFiles) { this.includeTestFiles = includeTestFiles; } + + private void checkOpenAPIVersion(OpenAPI openAPIDef) { + if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { + outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + + "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); + } + } } From 6c53a0ecf3ab9d02e1e780bf391563cd265de5ef Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 29 Aug 2023 16:51:39 +0530 Subject: [PATCH 04/16] Fix returning an unsupported message when an OpenAPI with unsupported version is given --- .../java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java index 32f60ecc2..cbbcca05e 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/BallerinaCodeGenerator.java @@ -475,7 +475,7 @@ public void setIncludeTestFiles(boolean includeTestFiles) { } private void checkOpenAPIVersion(OpenAPI openAPIDef) { - if (SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { + if (!SUPPORTED_OPENAPI_VERSIONS.contains(openAPIDef.getOpenapi())) { outStream.printf("WARNING: The tool has not been tested with OpenAPI version %s. " + "The generated code may potentially contain errors.%n", openAPIDef.getOpenapi()); } From 1799a4a1fba8e81727516eb5359134333d8b2d01 Mon Sep 17 00:00:00 2001 From: ballerina-bot Date: Fri, 1 Sep 2023 06:53:29 +0530 Subject: [PATCH 05/16] [Automated] Update dependencies --- gradle.properties | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/gradle.properties b/gradle.properties index 45e4fbf10..5bd7abf20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ group=io.ballerina version=1.8.0-SNAPSHOT #dependency -ballerinaLangVersion=2201.8.0-20230816-121900-c1174ddd +ballerinaLangVersion=2201.8.0-20230830-220400-8a7556d8 testngVersion=7.6.1 slf4jVersion=1.7.30 org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 @@ -15,41 +15,41 @@ swaggerParserVersion=2.1.16 puppycrawlCheckstyleVersion = 10.12.1 # Stdlib Level 01 -stdlibIoVersion=1.5.0 +stdlibIoVersion=1.6.0-20230831-135000-049f91a stdlibRegexVersion=1.4.3 -stdlibTimeVersion=2.3.0 -stdlibUrlVersion=2.3.0 -stdlibXmldataVersion=2.6.0 +stdlibTimeVersion=2.4.0-20230831-134800-62143cd +stdlibUrlVersion=2.4.0-20230831-134600-aab5a12 +stdlibXmldataVersion=2.7.0-20230831-135200-52e14b9 # Stdlib Level 02 -stdlibConstraintVersion=1.3.0 -stdlibCryptoVersion=2.4.0 -stdlibLogVersion=2.8.1-20230718-085900-36c385c -stdlibOsVersion=1.7.0 -stdlibTaskVersion=2.4.0 +stdlibConstraintVersion=1.4.0-20230831-142400-50e4023 +stdlibCryptoVersion=2.5.0-20230831-142400-f4a0d9f +stdlibLogVersion=2.9.0-20230831-153100-9b73447 +stdlibOsVersion=1.8.0-20230831-142200-cb96913 +stdlibTaskVersion=2.5.0-20230831-142600-ec5194a # Stdlib Level 03 -stdlibCacheVersion=3.6.0 -stdlibFileVersion=1.8.0 -stdlibMimeVersion=2.8.0 -stdlibUuidVersion=1.6.0 +stdlibCacheVersion=3.7.0-20230831-151700-7e2b0fa +stdlibFileVersion=1.9.0-20230831-160900-252d078 +stdlibMimeVersion=2.9.0-20230831-160900-6ecc648 +stdlibUuidVersion=1.7.0-20230831-151800-012c311 # Stdlib Level 04 -stdlibAuthVersion=2.9.0 -stdlibJwtVersion=2.9.0 -stdlibOAuth2Version=2.9.0 +stdlibAuthVersion=2.10.0-20230831-160500-17532df +stdlibJwtVersion=2.10.0-20230831-160800-b5db938 +stdlibOAuth2Version=2.10.0-20230831-161200-a4a83a6 # Stdlib Level 05 -stdlibHttpVersion=2.10.0-20230809-150400-0c9cad9 +stdlibHttpVersion=2.10.0-20230831-195300-180df44 # Stdlib Level 06 -stdlibGrpcVersion=1.9.1-20230809-211700-feffbef -stdlibWebsocketVersion=2.9.1-20230809-174700-6942521 -stdlibWebsubVersion=2.9.1-20230809-211400-c6c75d2 +stdlibGrpcVersion=1.10.0-20230831-221200-3f0bada +stdlibWebsocketVersion=2.10.0-20230831-212500-7bd0f7e +stdlibWebsubVersion=2.10.0-20230831-211200-c6f33c4 # Stdlib Level 07 -stdlibGraphqlVersion=1.10.0-20230809-214800-d775b0d +stdlibGraphqlVersion=1.10.0-20230831-232100-bb5b601 # Ballerinax Observer -observeVersion=1.1.0 -observeInternalVersion=1.1.0 +observeVersion=1.2.0-20230831-133600-6d16df4 +observeInternalVersion=1.2.0-20230831-141400-b3bbdc2 From ccab3177cde0936928233b762a561d7a4035c170 Mon Sep 17 00:00:00 2001 From: Aneesha Fernando <34091327+aneeshafedo@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:16:10 +0530 Subject: [PATCH 06/16] [master] Handling single valid item AllOf schema in client/service generation (#1525) * Add support for insgle schema allOf * Address reviews * Update the order in if statement * Fix test failure --- .../openapi/converter/Constants.java | 2 +- .../ballerina/openapi/cmd/CmdConstants.java | 2 +- .../client/BallerinaDiagnosticTests.java | 1 + .../generators/client/PathParameterTests.java | 3 +- .../openapi/generators/common/TestUtils.java | 7 -- .../generators/schema/AllOfDataTypeTests.java | 10 +++ .../service/ServiceDiagnosticTests.java | 4 +- .../diagnostic_files/single_allOf.yaml | 54 +++++++++++++++ .../schema/ballerina/single_item_allOf.bal | 19 ++++++ .../schema/swagger/single_item_allOf.yaml | 65 +++++++++++++++++++ .../client/FunctionSignatureGenerator.java | 9 ++- .../AllOfRecordTypeGenerator.java | 52 ++++++++++----- .../openapi/validator/Constants.java | 2 +- .../test/resources/return/response_code.yaml | 2 + 14 files changed, 202 insertions(+), 30 deletions(-) create mode 100644 openapi-cli/src/test/resources/generators/diagnostic_files/single_allOf.yaml create mode 100644 openapi-cli/src/test/resources/generators/schema/ballerina/single_item_allOf.bal create mode 100644 openapi-cli/src/test/resources/generators/schema/swagger/single_item_allOf.yaml diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java index 88f63b674..310ccc379 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/Constants.java @@ -189,7 +189,7 @@ public String toString() { httpCodeMap.put("NotImplemented", "501"); httpCodeMap.put("BadGateway", "502"); httpCodeMap.put("ServiceUnavailable", "503"); - httpCodeMap.put("GatewayTimeOut", "504"); + httpCodeMap.put("GatewayTimeout", "504"); httpCodeMap.put("HttpVersionNotSupported", "505"); httpCodeMap.put("VariantAlsoNegotiates", "506"); httpCodeMap.put("InsufficientStorage", "507"); diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java index 0bc09af59..04cc65396 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java @@ -170,7 +170,7 @@ public String getValue() { httpCodeMap.put("501", "NotImplemented"); httpCodeMap.put("502", "BadGateway"); httpCodeMap.put("503", "ServiceUnavailable"); - httpCodeMap.put("504", "GatewayTimeOut"); + httpCodeMap.put("504", "GatewayTimeout"); httpCodeMap.put("505", "HttpVersionNotSupported"); HTTP_CODES_DES = Collections.unmodifiableMap(httpCodeMap); } diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/BallerinaDiagnosticTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/BallerinaDiagnosticTests.java index bcd5c06bb..95cd3b862 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/BallerinaDiagnosticTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/BallerinaDiagnosticTests.java @@ -151,6 +151,7 @@ public Object[][] singleFileProviderForDiagnosticCheck() { {"duplicated_response.yaml"}, {"complex_oneOf_schema.yaml"}, {"request_body_ref.yaml"}, + {"single_allOf.yaml"}, {"vendor_specific_mime_types.yaml"}, {"requestBody_reference_has_inline_object_content_type.yaml"}, {"ballerinax_connector_tests/ably.yaml"}, diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/PathParameterTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/PathParameterTests.java index 0f3eb02fc..65ea5a6e5 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/PathParameterTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/client/PathParameterTests.java @@ -164,7 +164,8 @@ public void unionPathParameter() throws IOException, BallerinaOpenApiException { @Test(description = "When path parameter has given allOf data type in ballerina", expectedExceptions = BallerinaOpenApiException.class, - expectedExceptionsMessageRegExp = "Ballerina does not support object type path .*") + expectedExceptionsMessageRegExp = "Path parameter: 'id' is invalid. " + + "Ballerina does not support object type path parameters.") public void allOfPathParameter() throws IOException, BallerinaOpenApiException { Path definitionPath = RESDIR.resolve("swagger/allOf_path_parameter.yaml"); OpenAPI openAPI = GeneratorUtils.normalizeOpenAPI(definitionPath, true); diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/common/TestUtils.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/common/TestUtils.java index ccc1d97e4..5db762c11 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/common/TestUtils.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/common/TestUtils.java @@ -99,13 +99,6 @@ public static List getDiagnostics(SyntaxTree syntaxTree) throws Form return semanticModel.diagnostics(); } - public static List getDiagnosticsForGenericService(SyntaxTree serviceSyntaxTree) - throws FormatterException, IOException { - writeFile(servicePath, Formatter.format(serviceSyntaxTree).toSourceCode()); - SemanticModel semanticModel = getSemanticModel(servicePath); - return semanticModel.diagnostics(); - } - public static List getDiagnosticsForService(SyntaxTree serviceSyntaxTree, OpenAPI openAPI, BallerinaServiceGenerator ballerinaServiceGenerator) throws FormatterException, IOException, BallerinaOpenApiException { diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AllOfDataTypeTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AllOfDataTypeTests.java index 1b9590d96..46165238c 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AllOfDataTypeTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/schema/AllOfDataTypeTests.java @@ -114,6 +114,16 @@ public void generateNestedAllOfSchema() throws IOException, BallerinaOpenApiExce TestUtils.compareGeneratedSyntaxTreewithExpectedSyntaxTree("schema/ballerina/nested_all_of.bal", syntaxTree); } + @Test(description = "Generate type definition from allOf schema with valid single item") + public void generateAllOfwithValidSingleItem() throws IOException, BallerinaOpenApiException { + Path definitionPath = RES_DIR.resolve("swagger/single_item_allOf.yaml"); + OpenAPI openAPI = GeneratorUtils.normalizeOpenAPI(definitionPath, true); + BallerinaTypesGenerator ballerinaSchemaGenerator = new BallerinaTypesGenerator(openAPI); + syntaxTree = ballerinaSchemaGenerator.generateSyntaxTree(); + TestUtils.compareGeneratedSyntaxTreewithExpectedSyntaxTree( + "schema/ballerina/single_item_allOf.bal", syntaxTree); + } + @Test(description = "Tests record generation for nested OneOf schema inside AllOf schema", expectedExceptions = BallerinaOpenApiException.class, expectedExceptionsMessageRegExp = diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ServiceDiagnosticTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ServiceDiagnosticTests.java index 374524eb6..a69be9844 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ServiceDiagnosticTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ServiceDiagnosticTests.java @@ -41,7 +41,6 @@ import java.util.ArrayList; import java.util.List; -import static io.ballerina.openapi.generators.common.TestUtils.getDiagnosticsForGenericService; import static io.ballerina.openapi.generators.common.TestUtils.getDiagnosticsForService; import static io.ballerina.openapi.generators.common.TestUtils.normalizeOpenAPI; @@ -92,7 +91,7 @@ public void checkDiagnosticIssuesInGenericServiceGen(String yamlFile) throws IOE .build(); BallerinaServiceGenerator ballerinaServiceGenerator = new BallerinaServiceGenerator(oasServiceMetadata); syntaxTree = ballerinaServiceGenerator.generateSyntaxTree(); - List diagnostics = getDiagnosticsForGenericService(syntaxTree); + List diagnostics = getDiagnosticsForService(syntaxTree, openAPI, ballerinaServiceGenerator); boolean hasErrors = diagnostics.stream() .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); Assert.assertFalse(hasErrors); @@ -137,6 +136,7 @@ public Object[][] singleFileProviderForDiagnosticCheck() { {"complex_oneOf_schema.yaml"}, {"request_body_ref.yaml"}, {"vendor_specific_mime_types.yaml"}, + {"single_allOf.yaml"}, // TODO: Uncomment when fixed https://github.com/ballerina-platform/openapi-tools/issues/1415 // {"ballerinax_connector_tests/ably.yaml"}, {"ballerinax_connector_tests/azure.iot.yaml"}, diff --git a/openapi-cli/src/test/resources/generators/diagnostic_files/single_allOf.yaml b/openapi-cli/src/test/resources/generators/diagnostic_files/single_allOf.yaml new file mode 100644 index 000000000..de56207c6 --- /dev/null +++ b/openapi-cli/src/test/resources/generators/diagnostic_files/single_allOf.yaml @@ -0,0 +1,54 @@ +openapi: 3.0.1 +info: + title: Sample API + description: API description in Markdown. + version: 1.0.0 +servers: + - url: https://api.example.com +paths: + /users/{id}: + get: + summary: Returns a list of users. + operationId: getUserById + description: Optional extended description in Markdown. + parameters: + - name: id + in: path + description: ID of user to fetch + required: true + schema: + allOf: + - $ref: "#/components/schemas/Name" + - description: "Name details" + responses: + "200": + description: OK +components: + schemas: + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + email: + type: string + name: + type: string + address: + allOf: + - $ref: "#/components/schemas/Address" + - description: "abc" + xml: + name: User + Address: + type: object + properties: + street: + type: string + city: + type: string + Name: + type: string diff --git a/openapi-cli/src/test/resources/generators/schema/ballerina/single_item_allOf.bal b/openapi-cli/src/test/resources/generators/schema/ballerina/single_item_allOf.bal new file mode 100644 index 000000000..f1ae70a86 --- /dev/null +++ b/openapi-cli/src/test/resources/generators/schema/ballerina/single_item_allOf.bal @@ -0,0 +1,19 @@ +public type User record { + int id?; + string username?; + string email?; + string name?; + Address address?; + anydata remarks?; +}; + +public type Description anydata; + +public type Address record { + string street?; + string city?; +}; + +public type Id Name; + +public type Name string; \ No newline at end of file diff --git a/openapi-cli/src/test/resources/generators/schema/swagger/single_item_allOf.yaml b/openapi-cli/src/test/resources/generators/schema/swagger/single_item_allOf.yaml new file mode 100644 index 000000000..b1c8a675d --- /dev/null +++ b/openapi-cli/src/test/resources/generators/schema/swagger/single_item_allOf.yaml @@ -0,0 +1,65 @@ +openapi: 3.0.1 +info: + title: Sample API + description: API description in Markdown. + version: 1.0.0 +servers: + - url: https://api.example.com +paths: + /users/{id}: + get: + summary: Returns a list of users. + operationId: getUserById + description: Optional extended description in Markdown. + parameters: + - name: id + in: path + description: ID of user to fetch + required: true + schema: + allOf: + - $ref: "#/components/schemas/Name" + - description: "Name details" + - name: description + in: query + required: true + schema: + allOf: + - description: "Explain the user" + - description: "Name details" + responses: + "200": + description: OK +components: + schemas: + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + email: + type: string + name: + type: string + address: + allOf: + - $ref: "#/components/schemas/Address" + - description: "abc" + remarks: + allOf: + - description: "User status" + - description: "User data" + xml: + name: User + Address: + type: object + properties: + street: + type: string + city: + type: string + Name: + type: string diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/FunctionSignatureGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/FunctionSignatureGenerator.java index 06fb30532..04453cc3a 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/FunctionSignatureGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/FunctionSignatureGenerator.java @@ -33,6 +33,7 @@ import io.ballerina.compiler.syntax.tree.ReturnTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.Token; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; @@ -376,8 +377,12 @@ public Node getPathParameters(Parameter parameter, NodeList para if (parameterSchema.get$ref() != null) { type = getValidName(extractReferenceType(parameterSchema.get$ref()), true); Schema schema = openAPI.getComponents().getSchemas().get(type.trim()); - if (isObjectSchema(schema) || (isComposedSchema(schema) && schema.getAllOf() != null)) { - throw new BallerinaOpenApiException("Ballerina does not support object type path parameters."); + TypeDefinitionNode typeDefinitionNode = ballerinaSchemaGenerator.getTypeDefinitionNode + (schema, type, new ArrayList<>()); + if (typeDefinitionNode.typeDescriptor().kind().equals(SyntaxKind.RECORD_TYPE_DESC)) { + throw new BallerinaOpenApiException(String.format( + "Path parameter: '%s' is invalid. Ballerina does not support object type path parameters.", + parameter.getName())); } } else { type = convertOpenAPITypeToBallerina(parameter.getSchema()); diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/schema/ballerinatypegenerators/AllOfRecordTypeGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/schema/ballerinatypegenerators/AllOfRecordTypeGenerator.java index ea015d315..91c4179b0 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/schema/ballerinatypegenerators/AllOfRecordTypeGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/schema/ballerinatypegenerators/AllOfRecordTypeGenerator.java @@ -33,6 +33,7 @@ import io.ballerina.openapi.core.generators.schema.model.RecordMetadata; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.tuple.ImmutablePair; import java.util.ArrayList; import java.util.LinkedList; @@ -91,7 +92,7 @@ public TypeDescriptorNode generateTypeDescriptorNode() throws BallerinaOpenApiEx // This assertion is always `true` because this type generator receive ComposedSchema during the upper level // filtering as input. Has to use this assertion statement instead of `if` condition, because to avoid // unreachable else statement. - List allOfSchemas = schema.getAllOf(); + List> allOfSchemas = schema.getAllOf(); RecordMetadata recordMetadata = getRecordMetadata(); RecordRestDescriptorNode restDescriptorNode = recordMetadata.getRestDescriptorNode(); @@ -100,23 +101,37 @@ public TypeDescriptorNode generateTypeDescriptorNode() throws BallerinaOpenApiEx typeName); return referencedTypeGenerator.generateTypeDescriptorNode(); } else { - List recordFieldList = generateAllOfRecordFields(allOfSchemas); - addAdditionalSchemas(schema); - restDescriptorNode = - restSchemas.size() > 1 ? getRestDescriptorNodeForAllOf(restSchemas) : restDescriptorNode; - - NodeList fieldNodes = AbstractNodeFactory.createNodeList(recordFieldList); - return NodeFactory.createRecordTypeDescriptorNode(createToken(RECORD_KEYWORD), - recordMetadata.isOpenRecord() ? createToken(OPEN_BRACE_TOKEN) : createToken(OPEN_BRACE_PIPE_TOKEN), - fieldNodes, restDescriptorNode, - recordMetadata.isOpenRecord() ? createToken(CLOSE_BRACE_TOKEN) : - createToken(CLOSE_BRACE_PIPE_TOKEN)); + ImmutablePair, List>> recordFlist = generateAllOfRecordFields(allOfSchemas); + List recordFieldList = recordFlist.getLeft(); + List> validSchemas = recordFlist.getRight(); + if (validSchemas.isEmpty()) { + AnyDataTypeGenerator anyDataTypeGenerator = new AnyDataTypeGenerator(schema, typeName); + return anyDataTypeGenerator.generateTypeDescriptorNode(); + } else if (validSchemas.size() == 1) { + TypeGenerator typeGenerator = getTypeGenerator(validSchemas.get(0), typeName, null); + return typeGenerator.generateTypeDescriptorNode(); + } else { + addAdditionalSchemas(schema); + restDescriptorNode = + restSchemas.size() > 1 ? getRestDescriptorNodeForAllOf(restSchemas) : restDescriptorNode; + + NodeList fieldNodes = AbstractNodeFactory.createNodeList(recordFieldList); + return NodeFactory.createRecordTypeDescriptorNode(createToken(RECORD_KEYWORD), + recordMetadata.isOpenRecord() ? + createToken(OPEN_BRACE_TOKEN) : createToken(OPEN_BRACE_PIPE_TOKEN), + fieldNodes, restDescriptorNode, + recordMetadata.isOpenRecord() ? createToken(CLOSE_BRACE_TOKEN) : + createToken(CLOSE_BRACE_PIPE_TOKEN)); + } } } - private List generateAllOfRecordFields(List allOfSchemas) throws BallerinaOpenApiException { + private ImmutablePair, List>> generateAllOfRecordFields(List> allOfSchemas) + throws BallerinaOpenApiException { List recordFieldList = new ArrayList<>(); + List> validSchemas = new ArrayList<>(); + for (Schema allOfSchema : allOfSchemas) { if (allOfSchema.get$ref() != null) { String extractedSchemaName = GeneratorUtils.extractReferenceType(allOfSchema.get$ref()); @@ -137,15 +152,22 @@ private List generateAllOfRecordFields(List allOfSchemas) throws B addAdditionalSchemas(allOfSchema); } else if (GeneratorUtils.isComposedSchema(allOfSchema)) { if (allOfSchema.getAllOf() != null) { - recordFieldList.addAll(generateAllOfRecordFields(allOfSchema.getAllOf())); + ImmutablePair, List>> immutablePair = + generateAllOfRecordFields(allOfSchema.getAllOf()); + List recordAllFields = (List) immutablePair.getLeft(); + recordFieldList.addAll(recordAllFields); } else { // TODO: Needs to improve the error message. Could not access the schema name at this level. throw new BallerinaOpenApiException( "Unsupported nested OneOf or AnyOf schema is found inside a AllOf schema."); } } + if (allOfSchema.getType() != null || allOfSchema.getProperties() != null || allOfSchema.get$ref() != null + || allOfSchema.getAllOf() != null) { + validSchemas.add(allOfSchema); + } } - return recordFieldList; + return ImmutablePair.of(recordFieldList, validSchemas); } /** diff --git a/openapi-validator/src/main/java/io/ballerina/openapi/validator/Constants.java b/openapi-validator/src/main/java/io/ballerina/openapi/validator/Constants.java index 04b27c55d..5d91220d2 100644 --- a/openapi-validator/src/main/java/io/ballerina/openapi/validator/Constants.java +++ b/openapi-validator/src/main/java/io/ballerina/openapi/validator/Constants.java @@ -131,7 +131,7 @@ public class Constants { httpCodes.put("NotImplemented", "501"); httpCodes.put("BadGateway", "502"); httpCodes.put("ServiceUnavailable", "503"); - httpCodes.put("GatewayTimeOut", "504"); + httpCodes.put("GatewayTimeout", "504"); httpCodes.put("HttpVersionNotSupported", "505"); httpCodes.put("VariantAlsoNegotiates", "506"); httpCodes.put("InsufficientStorage", "507"); diff --git a/openapi-validator/src/test/resources/return/response_code.yaml b/openapi-validator/src/test/resources/return/response_code.yaml index 07c756f2a..78bfd446e 100644 --- a/openapi-validator/src/test/resources/return/response_code.yaml +++ b/openapi-validator/src/test/resources/return/response_code.yaml @@ -94,6 +94,8 @@ paths: description: BadGateway "503": description: ServiceUnavailable + "504": + description: GatewayTimeout "505": description: HttpVersionNotSupported components: {} From 7ae3ba4ffada18457e7e4d1302a48d36deaf7088 Mon Sep 17 00:00:00 2001 From: ballerina-bot Date: Tue, 12 Sep 2023 09:09:48 +0530 Subject: [PATCH 07/16] [Automated] Update dependencies --- gradle.properties | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5bd7abf20..4564a7195 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ group=io.ballerina version=1.8.0-SNAPSHOT #dependency -ballerinaLangVersion=2201.8.0-20230830-220400-8a7556d8 +ballerinaLangVersion=2201.8.0-20230908-135700-74a59dff testngVersion=7.6.1 slf4jVersion=1.7.30 org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 @@ -15,41 +15,41 @@ swaggerParserVersion=2.1.16 puppycrawlCheckstyleVersion = 10.12.1 # Stdlib Level 01 -stdlibIoVersion=1.6.0-20230831-135000-049f91a +stdlibIoVersion=1.6.0-20230911-134800-de06e28 stdlibRegexVersion=1.4.3 -stdlibTimeVersion=2.4.0-20230831-134800-62143cd -stdlibUrlVersion=2.4.0-20230831-134600-aab5a12 -stdlibXmldataVersion=2.7.0-20230831-135200-52e14b9 +stdlibTimeVersion=2.4.0-20230911-140200-7f96f1e +stdlibUrlVersion=2.4.0-20230911-140700-36451a2 +stdlibXmldataVersion=2.7.0-20230911-140600-3edd92d # Stdlib Level 02 -stdlibConstraintVersion=1.4.0-20230831-142400-50e4023 -stdlibCryptoVersion=2.5.0-20230831-142400-f4a0d9f -stdlibLogVersion=2.9.0-20230831-153100-9b73447 -stdlibOsVersion=1.8.0-20230831-142200-cb96913 -stdlibTaskVersion=2.5.0-20230831-142600-ec5194a +stdlibConstraintVersion=1.4.0-20230911-142700-8202202 +stdlibCryptoVersion=2.5.0-20230911-142900-8807d07 +stdlibLogVersion=2.9.0-20230911-150900-2f32c1a +stdlibOsVersion=1.8.0-20230911-142700-b182bf4 +stdlibTaskVersion=2.5.0-20230911-143300-237313e # Stdlib Level 03 -stdlibCacheVersion=3.7.0-20230831-151700-7e2b0fa -stdlibFileVersion=1.9.0-20230831-160900-252d078 -stdlibMimeVersion=2.9.0-20230831-160900-6ecc648 -stdlibUuidVersion=1.7.0-20230831-151800-012c311 +stdlibCacheVersion=3.7.0-20230911-145700-142f375 +stdlibFileVersion=1.9.0-20230911-153400-738b25e +stdlibMimeVersion=2.9.0-20230911-153200-3add04c +stdlibUuidVersion=1.7.0-20230911-150900-6c4771f # Stdlib Level 04 -stdlibAuthVersion=2.10.0-20230831-160500-17532df -stdlibJwtVersion=2.10.0-20230831-160800-b5db938 -stdlibOAuth2Version=2.10.0-20230831-161200-a4a83a6 +stdlibAuthVersion=2.10.0-20230911-153500-8c3c5cb +stdlibJwtVersion=2.10.0-20230911-153400-b5de47b +stdlibOAuth2Version=2.10.0-20230911-153600-6710ec0 # Stdlib Level 05 -stdlibHttpVersion=2.10.0-20230831-195300-180df44 +stdlibHttpVersion=2.10.0-20230911-204400-1c092fe # Stdlib Level 06 -stdlibGrpcVersion=1.10.0-20230831-221200-3f0bada -stdlibWebsocketVersion=2.10.0-20230831-212500-7bd0f7e -stdlibWebsubVersion=2.10.0-20230831-211200-c6f33c4 +stdlibGrpcVersion=1.10.0-20230911-215000-dc09f7e +stdlibWebsocketVersion=2.10.0-20230911-221500-317e2e7 +stdlibWebsubVersion=2.10.0-20230911-215500-06f9822 # Stdlib Level 07 -stdlibGraphqlVersion=1.10.0-20230831-232100-bb5b601 +stdlibGraphqlVersion=1.10.0-20230912-084000-2ef781d # Ballerinax Observer -observeVersion=1.2.0-20230831-133600-6d16df4 -observeInternalVersion=1.2.0-20230831-141400-b3bbdc2 +observeVersion=1.2.0-20230911-133500-b3d8db3 +observeInternalVersion=1.2.0-20230911-141700-4c0454a From cd64de25151da05d43e11cee18cc6982f5017595 Mon Sep 17 00:00:00 2001 From: ballerina-bot Date: Tue, 19 Sep 2023 03:58:37 +0000 Subject: [PATCH 08/16] Move dependencies to stable version --- gradle.properties | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4564a7195..71bace922 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ group=io.ballerina version=1.8.0-SNAPSHOT #dependency -ballerinaLangVersion=2201.8.0-20230908-135700-74a59dff +ballerinaLangVersion=2201.8.0 testngVersion=7.6.1 slf4jVersion=1.7.30 org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 @@ -15,41 +15,41 @@ swaggerParserVersion=2.1.16 puppycrawlCheckstyleVersion = 10.12.1 # Stdlib Level 01 -stdlibIoVersion=1.6.0-20230911-134800-de06e28 +stdlibIoVersion=1.6.0 stdlibRegexVersion=1.4.3 -stdlibTimeVersion=2.4.0-20230911-140200-7f96f1e -stdlibUrlVersion=2.4.0-20230911-140700-36451a2 -stdlibXmldataVersion=2.7.0-20230911-140600-3edd92d +stdlibTimeVersion=2.4.0 +stdlibUrlVersion=2.4.0 +stdlibXmldataVersion=2.7.0 # Stdlib Level 02 -stdlibConstraintVersion=1.4.0-20230911-142700-8202202 -stdlibCryptoVersion=2.5.0-20230911-142900-8807d07 -stdlibLogVersion=2.9.0-20230911-150900-2f32c1a -stdlibOsVersion=1.8.0-20230911-142700-b182bf4 -stdlibTaskVersion=2.5.0-20230911-143300-237313e +stdlibConstraintVersion=1.4.0 +stdlibCryptoVersion=2.5.0 +stdlibLogVersion=2.9.0 +stdlibOsVersion=1.8.0 +stdlibTaskVersion=2.5.0 # Stdlib Level 03 -stdlibCacheVersion=3.7.0-20230911-145700-142f375 -stdlibFileVersion=1.9.0-20230911-153400-738b25e -stdlibMimeVersion=2.9.0-20230911-153200-3add04c -stdlibUuidVersion=1.7.0-20230911-150900-6c4771f +stdlibCacheVersion=3.7.0 +stdlibFileVersion=1.9.0 +stdlibMimeVersion=2.9.0 +stdlibUuidVersion=1.7.0 # Stdlib Level 04 -stdlibAuthVersion=2.10.0-20230911-153500-8c3c5cb -stdlibJwtVersion=2.10.0-20230911-153400-b5de47b -stdlibOAuth2Version=2.10.0-20230911-153600-6710ec0 +stdlibAuthVersion=2.10.0 +stdlibJwtVersion=2.10.0 +stdlibOAuth2Version=2.10.0 # Stdlib Level 05 -stdlibHttpVersion=2.10.0-20230911-204400-1c092fe +stdlibHttpVersion=2.10.0 # Stdlib Level 06 -stdlibGrpcVersion=1.10.0-20230911-215000-dc09f7e -stdlibWebsocketVersion=2.10.0-20230911-221500-317e2e7 -stdlibWebsubVersion=2.10.0-20230911-215500-06f9822 +stdlibGrpcVersion=1.10.0 +stdlibWebsocketVersion=2.10.0 +stdlibWebsubVersion=2.10.0 # Stdlib Level 07 -stdlibGraphqlVersion=1.10.0-20230912-084000-2ef781d +stdlibGraphqlVersion=1.10.0 # Ballerinax Observer -observeVersion=1.2.0-20230911-133500-b3d8db3 -observeInternalVersion=1.2.0-20230911-141700-4c0454a +observeVersion=1.2.0 +observeInternalVersion=1.2.0 From adc81fee4b1e0fd7a84a4bf7e87da8a354ae9a32 Mon Sep 17 00:00:00 2001 From: ballerina-bot Date: Tue, 19 Sep 2023 04:34:28 +0000 Subject: [PATCH 09/16] [Gradle Release Plugin] - pre tag commit: 'v1.8.0'. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 71bace922..abb404c49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina -version=1.8.0-SNAPSHOT +version=1.8.0 #dependency ballerinaLangVersion=2201.8.0 From 92e3feb5d32dc096bc25e97230b1a348fc4ed698 Mon Sep 17 00:00:00 2001 From: ballerina-bot Date: Tue, 19 Sep 2023 04:34:31 +0000 Subject: [PATCH 10/16] [Gradle Release Plugin] - new version commit: 'v1.8.1-SNAPSHOT'. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index abb404c49..5210439f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina -version=1.8.0 +version=1.8.1-SNAPSHOT #dependency ballerinaLangVersion=2201.8.0 From deb8b15ec406df97b4506ef769a6eee6e72dffb6 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Thu, 21 Sep 2023 14:37:23 +0530 Subject: [PATCH 11/16] Add union type support for the request body mapping --- .../diagnostic/DiagnosticMessages.java | 5 +- .../service/OpenAPIRequestBodyMapper.java | 309 ++++++++++++++---- .../service/OpenAPIResourceMapper.java | 9 +- .../generators/openapi/RequestBodyTest.java | 6 + .../expected_gen/request_body/union_type.yaml | 176 ++++++++++ .../request_body/union_type.bal | 36 ++ 6 files changed, 468 insertions(+), 73 deletions(-) create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/request_body/union_type.yaml create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/request_body/union_type.bal diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/diagnostic/DiagnosticMessages.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/diagnostic/DiagnosticMessages.java index aa5a3c2b4..4bb67ca96 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/diagnostic/DiagnosticMessages.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/diagnostic/DiagnosticMessages.java @@ -63,7 +63,10 @@ public enum DiagnosticMessages { "for Ballerina type '%s'. ", DiagnosticSeverity.WARNING), OAS_CONVERTOR_115("OAS_CONVERTOR_115", "Given Ballerina file does not contain any HTTP service.", - DiagnosticSeverity.ERROR); + DiagnosticSeverity.ERROR), + OAS_CONVERTOR_116("OAS_CONVERTOR_116", "Generated OpenAPI definition does not contain `%s` request" + + " body information, as it's not supported by the OpenAPI tool.", + DiagnosticSeverity.WARNING); private final String code; private final String description; diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java index 27f724bbe..5fbb3cb20 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java @@ -19,24 +19,32 @@ package io.ballerina.openapi.converter.service; import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeDescKind; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.MapTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MappingFieldNode; -import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.OptionalTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; import io.ballerina.compiler.syntax.tree.RequiredParameterNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.UnionTypeDescriptorNode; import io.ballerina.openapi.converter.Constants; +import io.ballerina.openapi.converter.diagnostic.DiagnosticMessages; +import io.ballerina.openapi.converter.diagnostic.IncompatibleResourceDiagnostic; import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic; import io.ballerina.openapi.converter.utils.ConverterCommonUtils; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.ObjectSchema; import io.swagger.v3.oas.models.media.Schema; @@ -60,6 +68,7 @@ import static io.ballerina.openapi.converter.Constants.TEXT_PREFIX; import static io.ballerina.openapi.converter.Constants.XML_POSTFIX; import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.extractAnnotationFieldDetails; +import static io.ballerina.openapi.converter.utils.ConverterCommonUtils.getOpenApiSchema; /** * OpenAPIRequestBodyMapper provides functionality for converting ballerina payload to OAS request body model. @@ -70,7 +79,7 @@ public class OpenAPIRequestBodyMapper { private final Components components; private final OperationAdaptor operationAdaptor; private final SemanticModel semanticModel; - private final String customMediaType; + private final String customMediaPrefix; private final List diagnostics; /** @@ -86,7 +95,7 @@ public OpenAPIRequestBodyMapper(Components components, OperationAdaptor operatio this.components = components; this.operationAdaptor = operationAdaptor; this.semanticModel = semanticModel; - this.customMediaType = customMediaType; + this.customMediaPrefix = customMediaType; this.diagnostics = new ArrayList<>(); } @@ -140,8 +149,7 @@ public void handlePayloadAnnotation(RequiredParameterNode payloadNode, Map */ - private void handleSinglePayloadType(RequiredParameterNode payloadNode, Map schema, - RequestBody bodyParameter, String customMediaPrefix) { + private void handlePayloadType(TypeDescriptorNode payloadNode, Map schema, + RequestBody bodyParameter) { + SyntaxKind kind = payloadNode.kind(); + String mediaTypeString = getMediaTypeForSyntaxKind(payloadNode); + if (mediaTypeString != null || payloadNode.kind() == SyntaxKind.UNION_TYPE_DESC || + payloadNode.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE || + payloadNode.kind() == SyntaxKind.OPTIONAL_TYPE_DESC || + payloadNode.kind() == SyntaxKind.ARRAY_TYPE_DESC) { + switch (kind) { + case INT_TYPE_DESC: + case FLOAT_TYPE_DESC: + case DECIMAL_TYPE_DESC: + case BOOLEAN_TYPE_DESC: + case JSON_TYPE_DESC: + case XML_TYPE_DESC: + case BYTE_TYPE_DESC: + addConsumes(operationAdaptor, bodyParameter, mediaTypeString, kind); + break; + case STRING_TYPE_DESC: + mediaTypeString = customMediaPrefix == null ? MediaType.TEXT_PLAIN : + TEXT_PREFIX + customMediaPrefix + TEXT_POSTFIX; + addConsumes(operationAdaptor, bodyParameter, mediaTypeString, kind); + break; + case MAP_TYPE_DESC: + Schema objectSchema = new ObjectSchema(); + MapTypeDescriptorNode mapTypeDescriptorNode = (MapTypeDescriptorNode) payloadNode; + SyntaxKind mapMemberType = (mapTypeDescriptorNode.mapTypeParamsNode()).typeNode().kind(); + Schema openApiSchema = getOpenApiSchema(mapMemberType); + objectSchema.additionalProperties(openApiSchema); + io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType(); + if (bodyParameter.getContent() != null) { + Content content = bodyParameter.getContent(); + if (content.containsKey(mediaTypeString)) { + ComposedSchema oneOfSchema = new ComposedSchema(); + oneOfSchema.addOneOfItem(content.get(mediaTypeString).getSchema()); + oneOfSchema.addOneOfItem(objectSchema); + mediaType.setSchema(oneOfSchema); + content.addMediaType(mediaTypeString, mediaType); + } else { + mediaType.setSchema(objectSchema); + content.addMediaType(mediaTypeString, mediaType); + } + } else { + mediaType.setSchema(objectSchema); + bodyParameter.setContent(new Content().addMediaType(mediaTypeString, mediaType)); + } + operationAdaptor.getOperation().setRequestBody(bodyParameter); + break; + case OPTIONAL_TYPE_DESC: + OptionalTypeDescriptorNode optionalTypeDescriptorNode = (OptionalTypeDescriptorNode) payloadNode; + handlePayloadType((TypeDescriptorNode) optionalTypeDescriptorNode.typeDescriptor(), + schema, bodyParameter); + break; + case UNION_TYPE_DESC: + UnionTypeDescriptorNode unionTypeDescriptorNode = (UnionTypeDescriptorNode) payloadNode; + TypeDescriptorNode leftTypeDesc = unionTypeDescriptorNode.leftTypeDesc(); + TypeDescriptorNode rightTypeDesc = unionTypeDescriptorNode.rightTypeDesc(); + handlePayloadType(leftTypeDesc, schema, bodyParameter); + handlePayloadType(rightTypeDesc, schema, bodyParameter); + break; + case SIMPLE_NAME_REFERENCE: + SimpleNameReferenceNode record = (SimpleNameReferenceNode) payloadNode; + TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); + String recordName = record.name().toString().trim(); + handleReferencePayload(typeSymbol, mediaTypeString, recordName, schema, bodyParameter); + break; + case QUALIFIED_NAME_REFERENCE: + QualifiedNameReferenceNode separateRecord = (QualifiedNameReferenceNode) payloadNode; + typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(separateRecord)); + recordName = ((QualifiedNameReferenceNode) payloadNode).identifier().text(); + TypeDescKind typeDesc = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); + String mimeType = getMediaTypeForTypeDecKind(typeDesc); + + handleReferencePayload(typeSymbol, mimeType, recordName, schema, bodyParameter); + break; + case ARRAY_TYPE_DESC: + ArrayTypeDescriptorNode arrayNode = (ArrayTypeDescriptorNode) payloadNode; + mediaTypeString = getMediaTypeForSyntaxKind(arrayNode.memberTypeDesc()); + handleArrayTypePayload(schema, arrayNode, mediaTypeString, bodyParameter); + break; + default: + //Warning message for unsupported request payload type in Ballerina resource. + DiagnosticMessages errorMessage = DiagnosticMessages.OAS_CONVERTOR_116; + IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic(errorMessage, + payloadNode.location(), String.valueOf(payloadNode.kind())); + diagnostics.add(error); + break; + } + } else { + //Warning message for unsupported request payload type in Ballerina resource. + DiagnosticMessages errorMessage = DiagnosticMessages.OAS_CONVERTOR_116; + IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic(errorMessage, + payloadNode.location(), String.valueOf(payloadNode.kind())); + diagnostics.add(error); + } + } - String consumes = payloadNode.typeName().toString().trim(); - String mediaTypeString; - switch (consumes) { - case Constants.JSON: - mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_JSON : + /** + * This function is used to select the media type according to the syntax kind. + * + * @param payloadNode - payload type descriptor node + */ + private String getMediaTypeForSyntaxKind(TypeDescriptorNode payloadNode) { + SyntaxKind kind = payloadNode.kind(); + switch (kind) { + case RECORD_TYPE_DESC: + case MAP_TYPE_DESC: + case INT_TYPE_DESC: + case FLOAT_TYPE_DESC: + case DECIMAL_TYPE_DESC: + case BOOLEAN_TYPE_DESC: + case JSON_TYPE_DESC: + return customMediaPrefix == null ? MediaType.APPLICATION_JSON : APPLICATION_PREFIX + customMediaPrefix + JSON_POSTFIX; - addConsumes(operationAdaptor, bodyParameter, mediaTypeString); - break; - case Constants.XML: - mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_XML : + case XML_TYPE_DESC: + return customMediaPrefix == null ? MediaType.APPLICATION_XML : APPLICATION_PREFIX + customMediaPrefix + XML_POSTFIX; - addConsumes(operationAdaptor, bodyParameter, mediaTypeString); - break; - case Constants.STRING: - mediaTypeString = customMediaPrefix == null ? MediaType.TEXT_PLAIN : + case STRING_TYPE_DESC: + return customMediaPrefix == null ? MediaType.TEXT_PLAIN : TEXT_PREFIX + customMediaPrefix + TEXT_POSTFIX; - addConsumes(operationAdaptor, bodyParameter, mediaTypeString); - break; - case Constants.BYTE_ARRAY: - mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_OCTET_STREAM : + case BYTE_TYPE_DESC: + return customMediaPrefix == null ? MediaType.APPLICATION_OCTET_STREAM : APPLICATION_PREFIX + customMediaPrefix + OCTECT_STREAM_POSTFIX; - addConsumes(operationAdaptor, bodyParameter, mediaTypeString); + case SIMPLE_NAME_REFERENCE: + SimpleNameReferenceNode record = (SimpleNameReferenceNode) payloadNode; + TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); + if (typeSymbol instanceof TypeReferenceTypeSymbol) { + typeSymbol = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(); + } + if (typeSymbol instanceof IntersectionTypeSymbol) { + typeSymbol = ((IntersectionTypeSymbol) typeSymbol).effectiveTypeDescriptor(); + } + return getMediaTypeForTypeDecKind(typeSymbol.typeKind()); + default: + // Default the warning will be added in the handlePayloadType function. break; - case Constants.MAP_STRING: - mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_JSON : + } + return null; + } + + /** + * This function is used to select the media type according to the type descriptor kind. + */ + private String getMediaTypeForTypeDecKind(TypeDescKind type) { + switch (type) { + case RECORD: + case MAP: + case INT: + case INT_SIGNED32: + case INT_SIGNED16: + case FLOAT: + case DECIMAL: + case BOOLEAN: + case JSON: + return customMediaPrefix == null ? MediaType.APPLICATION_JSON : APPLICATION_PREFIX + customMediaPrefix + JSON_POSTFIX; - Schema objectSchema = new ObjectSchema(); - objectSchema.additionalProperties(new StringSchema()); - io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType(); - mediaType.setSchema(objectSchema); - bodyParameter.setContent(new Content().addMediaType(mediaTypeString, mediaType)); - operationAdaptor.getOperation().setRequestBody(bodyParameter); - break; + case XML: + return customMediaPrefix == null ? MediaType.APPLICATION_XML : + APPLICATION_PREFIX + customMediaPrefix + XML_POSTFIX; + case STRING: + return customMediaPrefix == null ? MediaType.TEXT_PLAIN : + TEXT_PREFIX + customMediaPrefix + TEXT_POSTFIX; + case BYTE: + return customMediaPrefix == null ? MediaType.APPLICATION_OCTET_STREAM : + APPLICATION_PREFIX + customMediaPrefix + OCTECT_STREAM_POSTFIX; default: - Node node = payloadNode.typeName(); - mediaTypeString = customMediaPrefix == null ? MediaType.APPLICATION_JSON : - APPLICATION_PREFIX + customMediaPrefix + JSON_POSTFIX; - if (node.kind() == SyntaxKind.SIMPLE_NAME_REFERENCE) { - SimpleNameReferenceNode record = (SimpleNameReferenceNode) node; - // Creating request body - required. - TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); - String recordName = record.name().toString().trim(); - handleReferencePayload(typeSymbol, recordName, schema, mediaTypeString, bodyParameter); - } else if (node instanceof ArrayTypeDescriptorNode) { - handleArrayTypePayload(schema, (ArrayTypeDescriptorNode) node, mediaTypeString, bodyParameter); - } else if (node.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE) { - QualifiedNameReferenceNode separateRecord = (QualifiedNameReferenceNode) node; - TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(separateRecord)); - String recordName = ((QualifiedNameReferenceNode) payloadNode.typeName()).identifier().text(); - handleReferencePayload(typeSymbol, recordName, schema, mediaTypeString, bodyParameter); - } + // Default return the null and the warning will be added in the handlePayloadType function. break; } + return null; } private TypeSymbol getReferenceTypeSymbol(Optional symbol) { @@ -224,6 +346,7 @@ private void handleArrayTypePayload(Map schema, ArrayTypeDescrip ArraySchema arraySchema = new ArraySchema(); TypeDescriptorNode typeDescriptorNode = arrayNode.memberTypeDesc(); // Nested array not allowed + io.swagger.v3.oas.models.media.MediaType media = new io.swagger.v3.oas.models.media.MediaType(); if (typeDescriptorNode.kind().equals(SyntaxKind.SIMPLE_NAME_REFERENCE)) { //handle record for components SimpleNameReferenceNode referenceNode = (SimpleNameReferenceNode) typeDescriptorNode; @@ -234,19 +357,36 @@ private void handleArrayTypePayload(Map schema, ArrayTypeDescrip Schema itemSchema = new Schema(); arraySchema.setItems(itemSchema.$ref(ConverterCommonUtils.unescapeIdentifier( referenceNode.name().text().trim()))); - io.swagger.v3.oas.models.media.MediaType media = new io.swagger.v3.oas.models.media.MediaType(); media.setSchema(arraySchema); - if (requestBody.getContent() != null) { - Content content = requestBody.getContent(); - content.addMediaType(mimeType, media); + } else if (typeDescriptorNode.kind() == SyntaxKind.BYTE_TYPE_DESC) { + StringSchema byteSchema = new StringSchema(); + byteSchema.setFormat("byte"); + media.setSchema(byteSchema); + } else { + Schema itemSchema = getOpenApiSchema(typeDescriptorNode.kind()); + arraySchema.setItems(itemSchema); + media.setSchema(arraySchema); + } + + if (requestBody.getContent() != null) { + Content content = requestBody.getContent(); + if (content.containsKey(mimeType)) { + ComposedSchema oneOfSchema = new ComposedSchema(); + oneOfSchema.addOneOfItem(content.get(mimeType).getSchema()); + oneOfSchema.addOneOfItem(arraySchema); + media.setSchema(oneOfSchema); } else { - requestBody.setContent(new Content().addMediaType(mimeType, media)); - } - // Adding conditional check for http delete operation as it cannot have body - // parameter. - if (!operationAdaptor.getHttpOperation().equalsIgnoreCase("delete")) { - operationAdaptor.getOperation().setRequestBody(requestBody); + content.addMediaType(mimeType, media); } + content.addMediaType(mimeType, media); + requestBody.setContent(content); + } else { + requestBody.setContent(new Content().addMediaType(mimeType, media)); + } + // Adding conditional check for http delete operation as it cannot have body + // parameter. + if (!operationAdaptor.getHttpOperation().equalsIgnoreCase("delete")) { + operationAdaptor.getOperation().setRequestBody(requestBody); } } @@ -258,7 +398,7 @@ private void createRequestBody(RequestBody bodyParameter, RequiredParameterNode SimpleNameReferenceNode record = (SimpleNameReferenceNode) payloadNode.typeName(); TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); String recordName = record.name().toString().trim(); - handleReferencePayload(typeSymbol, recordName, schema, mimeType, requestBody); + handleReferencePayload(typeSymbol, mimeType, recordName, schema, requestBody); } else if (payloadNode.typeName() instanceof ArrayTypeDescriptorNode) { ArrayTypeDescriptorNode arrayTypeDescriptorNode = (ArrayTypeDescriptorNode) payloadNode.typeName(); handleArrayTypePayload(schema, arrayTypeDescriptorNode, mimeType, requestBody); @@ -280,8 +420,8 @@ private void createRequestBody(RequestBody bodyParameter, RequiredParameterNode /** * Handle record type request payload. */ - private void handleReferencePayload(TypeSymbol typeSymbol, String recordName, - Map schema, String mediaType, RequestBody bodyParameter) { + private void handleReferencePayload(TypeSymbol typeSymbol, String mediaType, String recordName, + Map schema, RequestBody bodyParameter) { //handle record for components OpenAPIComponentMapper componentMapper = new OpenAPIComponentMapper(components); componentMapper.createComponentSchema(schema, typeSymbol); @@ -290,7 +430,18 @@ private void handleReferencePayload(TypeSymbol typeSymbol, String recordName, media.setSchema(new Schema().$ref(ConverterCommonUtils.unescapeIdentifier(recordName))); if (bodyParameter.getContent() != null) { Content content = bodyParameter.getContent(); - content.addMediaType(mediaType, media); + if (content.containsKey(mediaType)) { + io.swagger.v3.oas.models.media.MediaType mediaSchema = content.get(mediaType); + Schema currentSchema = mediaSchema.getSchema(); + ComposedSchema oneOf = new ComposedSchema(); + oneOf.addOneOfItem(currentSchema); + oneOf.addOneOfItem(new Schema().$ref(ConverterCommonUtils.unescapeIdentifier(recordName))); + mediaSchema.setSchema(oneOf); + content.addMediaType(mediaType, mediaSchema); + } else { + content.addMediaType(mediaType, media); + } + bodyParameter.setContent(content); } else { bodyParameter.setContent(new Content().addMediaType(mediaType, media)); } @@ -301,12 +452,30 @@ private void handleReferencePayload(TypeSymbol typeSymbol, String recordName, } } - private void addConsumes(OperationAdaptor operationAdaptor, RequestBody bodyParameter, String applicationType) { - String type = applicationType.split("/")[1]; + private void addConsumes(OperationAdaptor operationAdaptor, RequestBody bodyParameter, String applicationType, + SyntaxKind kindType) { + String type = kindType.name().split("_")[0].toLowerCase(Locale.ENGLISH); Schema schema = ConverterCommonUtils.getOpenApiSchema(type); io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType(); - mediaType.setSchema(schema); - bodyParameter.setContent(new Content().addMediaType(applicationType, mediaType)); - operationAdaptor.getOperation().setRequestBody(bodyParameter); + if (bodyParameter.getContent() != null) { + Content content = bodyParameter.getContent(); + if (content.containsKey(applicationType)) { + io.swagger.v3.oas.models.media.MediaType mediaSchema = content.get(applicationType); + Schema currentSchema = mediaSchema.getSchema(); + ComposedSchema oneOf = new ComposedSchema(); + oneOf.addOneOfItem(currentSchema); + oneOf.addOneOfItem(schema); + mediaSchema.setSchema(oneOf); + content.addMediaType(applicationType, mediaSchema); + } else { + mediaType.setSchema(schema); + content.addMediaType(applicationType, mediaType); + } + bodyParameter.setContent(content); + } else { + mediaType.setSchema(schema); + bodyParameter.setContent(new Content().addMediaType(applicationType, mediaType)); + operationAdaptor.getOperation().setRequestBody(bodyParameter); + } } } diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIResourceMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIResourceMapper.java index ae33ba95a..517bfcb47 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIResourceMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIResourceMapper.java @@ -34,6 +34,7 @@ import io.ballerina.openapi.converter.diagnostic.IncompatibleResourceDiagnostic; import io.ballerina.openapi.converter.diagnostic.OpenAPIConverterDiagnostic; import io.ballerina.openapi.converter.utils.ConverterCommonUtils; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; @@ -215,8 +216,12 @@ private Optional convertResourceToOperation(FunctionDefinition openAPIParameterMapper.getResourceInputs(components, semanticModel); if (openAPIParameterMapper.getErrors().size() > 1 || (openAPIParameterMapper.getErrors().size() == 1 && !openAPIParameterMapper.getErrors().get(0).getCode().equals("OAS_CONVERTOR_113"))) { - errors.addAll(openAPIParameterMapper.getErrors()); - return Optional.empty(); + boolean isErrorIncluded = openAPIParameterMapper.getErrors().stream().anyMatch(d -> + DiagnosticSeverity.ERROR.equals(d.getDiagnosticSeverity())); + if (isErrorIncluded) { + errors.addAll(openAPIParameterMapper.getErrors()); + return Optional.empty(); + } } errors.addAll(openAPIParameterMapper.getErrors()); diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RequestBodyTest.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RequestBodyTest.java index c7306a645..77806e859 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RequestBodyTest.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/RequestBodyTest.java @@ -264,6 +264,12 @@ public void testForServiceConfigOnlyWithCors() { compareWithGeneratedFile(ballerinaFilePath, "service_config_with_cors.yaml"); } + @Test(description = "Generate OpenAPI spec for request body with union type") + public void testForUnionTypeRequestBody() { + Path ballerinaFilePath = RES_DIR.resolve("request_body/union_type.bal"); + compareWithGeneratedFile(ballerinaFilePath, "union_type.yaml"); + } + @AfterMethod public void cleanUp() { deleteDirectory(this.tempDir); diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/request_body/union_type.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/request_body/union_type.yaml new file mode 100644 index 000000000..5921bf820 --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/request_body/union_type.yaml @@ -0,0 +1,176 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 0.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "0" +paths: + /path: + post: + operationId: postPath + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ABC' + responses: + "202": + description: Accepted + /path2: + post: + operationId: postPath2 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ABC' + application/xml: + schema: {} + responses: + "202": + description: Accepted + /path3: + post: + operationId: postPath3 + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ABC' + - $ref: '#/components/schemas/Remote' + application/xml: + schema: {} + responses: + "202": + description: Accepted + /path4: + post: + operationId: postPath4 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ABC' + text/plain: + schema: + type: string + application/xml: + schema: {} + responses: + "202": + description: Accepted + /path5: + post: + operationId: postPath5 + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/ABC' + - type: integer + format: int64 + responses: + "202": + description: Accepted + /path6: + post: + operationId: postPath6 + requestBody: + content: + application/json: + schema: + type: integer + format: int64 + text/plain: + schema: + type: string + responses: + "202": + description: Accepted + /path7: + post: + operationId: postPath7 + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + additionalProperties: {} + - type: array + items: + type: integer + format: int64 + responses: + "202": + description: Accepted + /path8: + post: + operationId: postPath8 + requestBody: + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int64 + text/plain: + schema: + type: string + responses: + "202": + description: Accepted + /path9: + post: + operationId: postPath9 + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + additionalProperties: + type: integer + format: int64 + - type: object + additionalProperties: + type: string + responses: + "202": + description: Accepted +components: + schemas: + ABC: + required: + - id + - name + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + Remote: + required: + - host + - ip + - port + type: object + properties: + host: + type: string + port: + type: integer + format: int64 + ip: + type: string + description: Presents a read-only view of the remote address. diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/request_body/union_type.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/request_body/union_type.bal new file mode 100644 index 000000000..c0b255cd2 --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/request_body/union_type.bal @@ -0,0 +1,36 @@ +import ballerina/http; + +public type ABC record { + int id; + string name; +}; + +service /payloadV on new http:Listener(0) { + resource function post path(ABC? payload) { + } + resource function post path2(ABC|xml payload) { + } + resource function post path3(ABC|http:Remote|xml? payload) { + } + resource function post path4(@http:Payload ABC|string|xml payload) { + } + //oneOF- scenarios for application/json + resource function post path5(@http:Payload ABC|int payload) { + } + resource function post path6(@http:Payload int|string payload) { + } + resource function post path7(@http:Payload map|int[] payload) { + } + resource function post path8(@http:Payload map|string payload) { + } + resource function post path9(@http:Payload map|map payload) { + } + + // //negative - skip + // resource function post path10(ABC|string payload) { + // } + // resource function post path11(int|string payload) { + // } + // resource function post path12(map|string payload) { + // } +} From 213d3e3838ce199712f5d0a24d321617db6d99c2 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 22 Sep 2023 09:58:46 +0530 Subject: [PATCH 12/16] Fix review suggestions --- .../service/OpenAPIRequestBodyMapper.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java index 5fbb3cb20..e90525bfc 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java @@ -60,6 +60,7 @@ import javax.ws.rs.core.MediaType; import static io.ballerina.openapi.converter.Constants.APPLICATION_PREFIX; +import static io.ballerina.openapi.converter.Constants.DELETE; import static io.ballerina.openapi.converter.Constants.HTTP_PAYLOAD; import static io.ballerina.openapi.converter.Constants.JSON_POSTFIX; import static io.ballerina.openapi.converter.Constants.MEDIA_TYPE; @@ -164,10 +165,9 @@ private void handlePayloadType(TypeDescriptorNode payloadNode, Map Date: Mon, 25 Sep 2023 11:07:33 +0530 Subject: [PATCH 13/16] Add to-do for the handle union type defined in separate type --- .../openapi/converter/service/OpenAPIRequestBodyMapper.java | 2 +- .../expected_gen/request_body/union_type.yaml | 6 ++++++ .../ballerina-to-openapi/request_body/union_type.bal | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java index e90525bfc..9308f66e3 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java @@ -251,7 +251,7 @@ private void handlePayloadType(TypeDescriptorNode payloadNode, Map Date: Tue, 26 Sep 2023 16:37:58 +0530 Subject: [PATCH 14/16] Fix issue with data type when the path segment has parameter --- .../service/ParameterGeneratorTest.java | 15 ++- .../multiPathParamWithExtensionType.bal | 29 ++++++ .../multiPathParamWithExtensionType.yaml | 92 +++++++++++++++++++ .../openapi/core/GeneratorUtils.java | 2 +- 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 openapi-cli/src/test/resources/generators/service/ballerina/multiPathParamWithExtensionType.bal create mode 100644 openapi-cli/src/test/resources/generators/service/swagger/multiPathParamWithExtensionType.yaml diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ParameterGeneratorTest.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ParameterGeneratorTest.java index f196e0489..503bc270a 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ParameterGeneratorTest.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/service/ParameterGeneratorTest.java @@ -262,5 +262,18 @@ public void testsRefParamsInOpenAPIV31() throws IOException, BallerinaOpenApiExc CommonTestFunctions.compareGeneratedSyntaxTreewithExpectedSyntaxTree( "parameter_with_ref_v31.bal", syntaxTree); } -} + @Test(description = "Tests for path segments has parameters with extension") + public void testsForPathSegmentHasExtensionType() throws IOException, BallerinaOpenApiException { + Path definitionPath = RES_DIR.resolve("swagger/multiPathParamWithExtensionType.yaml"); + OpenAPI openAPI = GeneratorUtils.getOpenAPIFromOpenAPIV3Parser(definitionPath); + OASServiceMetadata oasServiceMetadata = new OASServiceMetadata.Builder() + .withOpenAPI(openAPI) + .withFilters(filter) + .build(); + BallerinaServiceGenerator ballerinaServiceGenerator = new BallerinaServiceGenerator(oasServiceMetadata); + syntaxTree = ballerinaServiceGenerator.generateSyntaxTree(); + CommonTestFunctions.compareGeneratedSyntaxTreewithExpectedSyntaxTree( + "multiPathParamWithExtensionType.bal", syntaxTree); + } +} diff --git a/openapi-cli/src/test/resources/generators/service/ballerina/multiPathParamWithExtensionType.bal b/openapi-cli/src/test/resources/generators/service/ballerina/multiPathParamWithExtensionType.bal new file mode 100644 index 000000000..00ed9830b --- /dev/null +++ b/openapi-cli/src/test/resources/generators/service/ballerina/multiPathParamWithExtensionType.bal @@ -0,0 +1,29 @@ +import ballerina/http; + +listener http:Listener ep0 = new (80, config = {host: "petstore.openapi.io"}); + +service /v1 on ep0 { + # Info for a specific pet + # + # + spreadsheetId - The id of the pet to retrieve + # + sheetidCopyto - The id of the pet to retrieve + # + return - returns can be any of following types + # http:Ok (Expected response to a valid request) + # http:Response (unexpected error) + resource function get v4/spreadsheets/[int spreadsheetId]/sheets/[string sheetidCopyto]() returns http:Ok|http:Response|error { + if !sheetidCopyto.endsWith(":copyTo") { + return error("bad URL"); + } + string sheetId = sheetidCopyto.substring(0, sheetidCopyto.length() - 6); + } + # Get the details of the specified field + # + # + idJson - Field ID + # + return - Successful response + resource function get 'field/[string idJson]() returns http:Ok|error { + if !idJson.endsWith(".json") { + return error("bad URL"); + } + string id = idJson.substring(0, idJson.length() - 4); + } +} diff --git a/openapi-cli/src/test/resources/generators/service/swagger/multiPathParamWithExtensionType.yaml b/openapi-cli/src/test/resources/generators/service/swagger/multiPathParamWithExtensionType.yaml new file mode 100644 index 000000000..12f0b4b28 --- /dev/null +++ b/openapi-cli/src/test/resources/generators/service/swagger/multiPathParamWithExtensionType.yaml @@ -0,0 +1,92 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OpenApi Petstore + license: + name: MIT +servers: + - url: http://petstore.{host}.io/v1 + description: The production API server + variables: + host: + default: openapi + description: this value is assigned by the service provider + +tags: + - name: pets + description: Pets Tag + - name: list + description: List Tag + +paths: + /v4/spreadsheets/{spreadsheetId}/sheets/{sheetId}:copyTo: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: spreadsheetId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: integer + - name: sheetId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: integer + responses: + '200': + description: Expected response to a valid request + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /field/{id}.json: + get: + tags: + - Field + operationId: getFieldById + description: Get the details of the specified field + parameters: + - description: Field ID + in: path + name: id + schema: + type: number + format: double + required: true + responses: + '200': + description: Successful response +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + type: + type: string + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/GeneratorUtils.java b/openapi-core/src/main/java/io/ballerina/openapi/core/GeneratorUtils.java index 3dcaee768..dc0facb62 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/GeneratorUtils.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/GeneratorUtils.java @@ -292,7 +292,7 @@ private static void extractPathParameterDetails(Operation operation, List // TypeDescriptor BuiltinSimpleNameReferenceNode builtSNRNode = createBuiltinSimpleNameReferenceNode( null, - parameter.getSchema() == null ? + parameter.getSchema() == null || hasSpecialCharacter ? createIdentifierToken(STRING) : createIdentifierToken(paramType)); IdentifierToken paramName = createIdentifierToken( From fb7a40391a0fac68d63db2aaebf2ca3f424f19e7 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Fri, 6 Oct 2023 11:38:19 +0530 Subject: [PATCH 15/16] Fix conflicts with diagnostic --- .../openapi/converter/service/OpenAPIRequestBodyMapper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java index 1fcadb303..3257586eb 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java @@ -245,15 +245,15 @@ private void handlePayloadType(TypeDescriptorNode payloadNode, Map Date: Tue, 10 Oct 2023 14:52:04 +0530 Subject: [PATCH 16/16] Fix review suggestions --- .../service/OpenAPIRequestBodyMapper.java | 172 +++++++++--------- 1 file changed, 88 insertions(+), 84 deletions(-) diff --git a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java index 3257586eb..e12b1dfba 100644 --- a/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java +++ b/openapi-bal-service/src/main/java/io/ballerina/openapi/converter/service/OpenAPIRequestBodyMapper.java @@ -167,95 +167,99 @@ private void handlePayloadType(TypeDescriptorNode payloadNode, Map openApiSchema = getOpenApiSchema(mapMemberType); - objectSchema.additionalProperties(openApiSchema); - io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType(); - if (bodyParameter.getContent() != null) { - Content content = bodyParameter.getContent(); - if (content.containsKey(mediaTypeString)) { - ComposedSchema oneOfSchema = new ComposedSchema(); - oneOfSchema.addOneOfItem(content.get(mediaTypeString).getSchema()); - oneOfSchema.addOneOfItem(objectSchema); - mediaType.setSchema(oneOfSchema); - content.addMediaType(mediaTypeString, mediaType); - } else { - mediaType.setSchema(objectSchema); - content.addMediaType(mediaTypeString, mediaType); - } - } else { - mediaType.setSchema(objectSchema); - bodyParameter.setContent(new Content().addMediaType(mediaTypeString, mediaType)); - } - operationAdaptor.getOperation().setRequestBody(bodyParameter); - break; - case OPTIONAL_TYPE_DESC: - OptionalTypeDescriptorNode optionalTypeDescriptorNode = (OptionalTypeDescriptorNode) payloadNode; - handlePayloadType((TypeDescriptorNode) optionalTypeDescriptorNode.typeDescriptor(), - schema, bodyParameter); - break; - case UNION_TYPE_DESC: - UnionTypeDescriptorNode unionTypeDescriptorNode = (UnionTypeDescriptorNode) payloadNode; - TypeDescriptorNode leftTypeDesc = unionTypeDescriptorNode.leftTypeDesc(); - TypeDescriptorNode rightTypeDesc = unionTypeDescriptorNode.rightTypeDesc(); - handlePayloadType(leftTypeDesc, schema, bodyParameter); - handlePayloadType(rightTypeDesc, schema, bodyParameter); - break; - case SIMPLE_NAME_REFERENCE: - SimpleNameReferenceNode record = (SimpleNameReferenceNode) payloadNode; - TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); - String recordName = record.name().toString().trim(); - handleReferencePayload(typeSymbol, mediaTypeString, recordName, schema, bodyParameter); - break; - case QUALIFIED_NAME_REFERENCE: - QualifiedNameReferenceNode separateRecord = (QualifiedNameReferenceNode) payloadNode; - typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(separateRecord)); - recordName = ((QualifiedNameReferenceNode) payloadNode).identifier().text(); - TypeDescKind typeDesc = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); - String mimeType = getMediaTypeForTypeDecKind(typeDesc); - - handleReferencePayload(typeSymbol, mimeType, recordName, schema, bodyParameter); - break; - case ARRAY_TYPE_DESC: - ArrayTypeDescriptorNode arrayNode = (ArrayTypeDescriptorNode) payloadNode; - mediaTypeString = getMediaTypeForSyntaxKind(arrayNode.memberTypeDesc()); - handleArrayTypePayload(schema, arrayNode, mediaTypeString, bodyParameter); - break; - default: - //Warning message for unsupported request payload type in Ballerina resource. - IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic( - DiagnosticMessages.OAS_CONVERTOR_117, payloadNode.location(), - payloadNode.toSourceCode().trim()); - diagnostics.add(error); - break; - } - } else { + if (!isTypeDeclarationExtractable(kind, mediaTypeString)) { //Warning message for unsupported request payload type in Ballerina resource. IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic( DiagnosticMessages.OAS_CONVERTOR_117, payloadNode.location(), payloadNode.toSourceCode().trim()); diagnostics.add(error); + return; } + switch (kind) { + case INT_TYPE_DESC: + case FLOAT_TYPE_DESC: + case DECIMAL_TYPE_DESC: + case BOOLEAN_TYPE_DESC: + case JSON_TYPE_DESC: + case XML_TYPE_DESC: + case BYTE_TYPE_DESC: + addConsumes(operationAdaptor, bodyParameter, mediaTypeString, kind); + break; + case STRING_TYPE_DESC: + mediaTypeString = customMediaPrefix == null ? MediaType.TEXT_PLAIN : + TEXT_PREFIX + customMediaPrefix + TEXT_POSTFIX; + addConsumes(operationAdaptor, bodyParameter, mediaTypeString, kind); + break; + case MAP_TYPE_DESC: + Schema objectSchema = new ObjectSchema(); + MapTypeDescriptorNode mapTypeDescriptorNode = (MapTypeDescriptorNode) payloadNode; + SyntaxKind mapMemberType = (mapTypeDescriptorNode.mapTypeParamsNode()).typeNode().kind(); + Schema openApiSchema = getOpenApiSchema(mapMemberType); + objectSchema.additionalProperties(openApiSchema); + io.swagger.v3.oas.models.media.MediaType mediaType = new io.swagger.v3.oas.models.media.MediaType(); + if (bodyParameter.getContent() != null) { + Content content = bodyParameter.getContent(); + if (content.containsKey(mediaTypeString)) { + ComposedSchema oneOfSchema = new ComposedSchema(); + oneOfSchema.addOneOfItem(content.get(mediaTypeString).getSchema()); + oneOfSchema.addOneOfItem(objectSchema); + mediaType.setSchema(oneOfSchema); + content.addMediaType(mediaTypeString, mediaType); + } else { + mediaType.setSchema(objectSchema); + content.addMediaType(mediaTypeString, mediaType); + } + } else { + mediaType.setSchema(objectSchema); + bodyParameter.setContent(new Content().addMediaType(mediaTypeString, mediaType)); + } + operationAdaptor.getOperation().setRequestBody(bodyParameter); + break; + case OPTIONAL_TYPE_DESC: + OptionalTypeDescriptorNode optionalTypeDescriptorNode = (OptionalTypeDescriptorNode) payloadNode; + handlePayloadType((TypeDescriptorNode) optionalTypeDescriptorNode.typeDescriptor(), + schema, bodyParameter); + break; + case UNION_TYPE_DESC: + UnionTypeDescriptorNode unionTypeDescriptorNode = (UnionTypeDescriptorNode) payloadNode; + TypeDescriptorNode leftTypeDesc = unionTypeDescriptorNode.leftTypeDesc(); + TypeDescriptorNode rightTypeDesc = unionTypeDescriptorNode.rightTypeDesc(); + handlePayloadType(leftTypeDesc, schema, bodyParameter); + handlePayloadType(rightTypeDesc, schema, bodyParameter); + break; + case SIMPLE_NAME_REFERENCE: + SimpleNameReferenceNode record = (SimpleNameReferenceNode) payloadNode; + TypeSymbol typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(record)); + String recordName = record.name().toString().trim(); + handleReferencePayload(typeSymbol, mediaTypeString, recordName, schema, bodyParameter); + break; + case QUALIFIED_NAME_REFERENCE: + QualifiedNameReferenceNode separateRecord = (QualifiedNameReferenceNode) payloadNode; + typeSymbol = getReferenceTypeSymbol(semanticModel.symbol(separateRecord)); + recordName = ((QualifiedNameReferenceNode) payloadNode).identifier().text(); + TypeDescKind typeDesc = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor().typeKind(); + String mimeType = getMediaTypeForTypeDecKind(typeDesc); + + handleReferencePayload(typeSymbol, mimeType, recordName, schema, bodyParameter); + break; + case ARRAY_TYPE_DESC: + ArrayTypeDescriptorNode arrayNode = (ArrayTypeDescriptorNode) payloadNode; + mediaTypeString = getMediaTypeForSyntaxKind(arrayNode.memberTypeDesc()); + handleArrayTypePayload(schema, arrayNode, mediaTypeString, bodyParameter); + break; + default: + //Warning message for unsupported request payload type in Ballerina resource. + IncompatibleResourceDiagnostic error = new IncompatibleResourceDiagnostic( + DiagnosticMessages.OAS_CONVERTOR_117, payloadNode.location(), + payloadNode.toSourceCode().trim()); + diagnostics.add(error); + break; + } + } + + private static boolean isTypeDeclarationExtractable(SyntaxKind kind, String mediaTypeString) { + return mediaTypeString != null || kind == SyntaxKind.UNION_TYPE_DESC || + kind == SyntaxKind.QUALIFIED_NAME_REFERENCE || kind == SyntaxKind.OPTIONAL_TYPE_DESC || + kind == SyntaxKind.ARRAY_TYPE_DESC; } /**