From f9a6a1f7809b42e87d9be3f70906c6de7b147232 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Mon, 12 Aug 2024 14:12:10 +0530 Subject: [PATCH 1/3] Fix issue with commented values. --- .../mapper/metainfo/MetaInfoMapperImpl.java | 24 +++- .../generators/openapi/ExampleTests.java | 60 ++++++++++ .../metainfo/examples/example.json | 7 ++ .../examples/resource_annotation_examples.bal | 69 +++++++++++ .../resource_annotation_examples.yaml | 108 ++++++++++++++++++ openapi-cli/src/test/resources/testng.xml | 1 + 6 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/example.json create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.bal create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.yaml diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java index 810e60642..5c6db6179 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/metainfo/MetaInfoMapperImpl.java @@ -66,6 +66,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static io.ballerina.openapi.service.mapper.Constants.EXAMPLES; import static io.ballerina.openapi.service.mapper.Constants.FILE_PATH; @@ -178,7 +180,7 @@ private static void handleExamples(ResourceMetaInfoAnnotation.Builder resMetaInf continue; } ExpressionNode expressValue = optExamplesValue.get(); - String sourceCode = expressValue.toSourceCode(); + String sourceCode = removeCommentLines(expressValue.toSourceCode()); ObjectMapper objectMapper = new ObjectMapper(); try { Map objectMap = objectMapper.readValue(sourceCode, Map.class); @@ -194,7 +196,7 @@ private static void handleExamples(ResourceMetaInfoAnnotation.Builder resMetaInf continue; } ExpressionNode expressValue = optExamplesValue.get(); - String mediaType = expressValue.toSourceCode(); + String mediaType = removeCommentLines(expressValue.toSourceCode()); ObjectMapper objectMapper = new ObjectMapper(); try { Map objectMap = objectMapper.readValue(mediaType, Map.class); @@ -507,4 +509,22 @@ private static List extractListItems(ListConstructorExpressionNode list) } return values; } + + public static String removeCommentLines(String content) { + // Use regex to match lines that start with "//" or "#" or contain inline "//" comments + String regex = "(?m)^\\s*(//|#).*|(?m)\\s*//.*$"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(content); + + // Remove lines that start with "//" or "#" + content = matcher.replaceAll(""); + + // Use regex to match and remove inline "//" comments + regex = "(?m)^(.*?)(//|#).*?$"; + pattern = Pattern.compile(regex); + matcher = pattern.matcher(content); + + // Replace inline comments with the content before them + return matcher.replaceAll("$1"); + } } diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java new file mode 100644 index 000000000..0269021b4 --- /dev/null +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.openapi.generators.openapi; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * This test class for the covering the unit tests for return type scenarios. + */ +public class ExampleTests { + private static final Path RES_DIR = Paths.get("src/test/resources/ballerina-to-openapi").toAbsolutePath(); + private Path tempDir; + + @BeforeMethod + public void setup() throws IOException { + this.tempDir = Files.createTempDirectory("bal-to-openapi-test-out-" + System.nanoTime()); + } + + //TODO: Enable this test once the openapi package is published with new changes + @Test(description = "Resource function api doc mapped to OAS operation summary", enabled = false) + public void testsForResourceFunction() throws IOException { + Path ballerinaFilePath = RES_DIR.resolve("metainfo/examples/resource_annotation_examples.bal"); + TestUtils.compareWithGeneratedFile(ballerinaFilePath, "metainfo/examples/" + + "resource_annotation_examples.yaml"); + } + + @AfterMethod + public void cleanUp() { + TestUtils.deleteDirectory(this.tempDir); + } + + @AfterTest + public void clean() { + System.setErr(null); + System.setOut(null); + } +} diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/example.json b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/example.json new file mode 100644 index 000000000..65d9fee8f --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/example.json @@ -0,0 +1,7 @@ +{ + "fromCurrency": "Same-EUR", + "toCurrency": "LKR", + "fromAmount": 200, + "toAmount": 60000, + "timestamp": "2024-07-14" +} \ No newline at end of file diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.bal new file mode 100644 index 000000000..bc07980fc --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.bal @@ -0,0 +1,69 @@ +import ballerina/http; +import ballerina/openapi; + +listener http:Listener httpListener = check new (9000); + +service /convert on httpListener { + + @openapi:ResourceInfo { + examples: { + "response": { + "201": { + "examples": { + "application/json": { + "json01": { + "filePath": "example.json" + }, + "json02": { + "value": { + "fromCurrency": "EUR", + "toCurrency": "LKR", + "fromAmount": 200, + "toAmount": 60000, + "timestamp": "2024-07-14" + } + } + // "json03": { + // "filePath": "example.json" + // }, + }, + "application/xml": { + "xml1": { + "filePath": "example.json" + }, + "xml2": { + "value": { + "fromCurrency": "EUR", + "toCurrency": "LKR", + "fromAmount": 200, + "toAmount": 60000, + "timestamp": "2024-07-14" + } + } + } + } + } + }, + "requestBody": { + "application/json": { + "requestExample01": { + "filePath": "example.json" + }, + "requestExample02": { + "value": { + "fromCurrancy": "LKR", + "toCurrancy": "USD" //comment + } + } + // "requestExample03": { + // "filePath": "example.json" + // }, + } + } + + } + } + resource function post rate(record {} payload) returns record {}|xml? { + return {}; + } +} diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.yaml new file mode 100644 index 000000000..b0b26029c --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/metainfo/examples/resource_annotation_examples.yaml @@ -0,0 +1,108 @@ +openapi: 3.0.1 +info: + title: Convert + version: 0.1.0 +servers: + - url: "{server}:{port}/convert" + variables: + server: + default: http://localhost + port: + default: "9000" +paths: + /rate: + post: + operationId: postRate + requestBody: + content: + application/json: + schema: + type: object + properties: {} + examples: + requestExample01: + value: + toAmount: 60000 + fromCurrency: Same-EUR + toCurrency: LKR + fromAmount: 200 + timestamp: 2024-07-14 + requestExample02: + value: + fromCurrancy: LKR + toCurrancy: USD + required: true + responses: + "201": + description: Created + content: + application/xml: + schema: + type: object + examples: + xml1: + value: + toAmount: 60000 + fromCurrency: Same-EUR + toCurrency: LKR + fromAmount: 200 + timestamp: 2024-07-14 + xml2: + value: + fromCurrency: EUR + toCurrency: LKR + fromAmount: 200 + toAmount: 60000 + timestamp: 2024-07-14 + application/json: + schema: + type: object + properties: {} + examples: + json02: + value: + fromCurrency: EUR + toCurrency: LKR + fromAmount: 200 + toAmount: 60000 + timestamp: 2024-07-14 + json01: + value: + toAmount: 60000 + fromCurrency: Same-EUR + toCurrency: LKR + fromAmount: 200 + timestamp: 2024-07-14 + "202": + description: Accepted + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' +components: + schemas: + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + timestamp: + type: string + status: + type: integer + format: int64 + reason: + type: string + message: + type: string + path: + type: string + method: + type: string diff --git a/openapi-cli/src/test/resources/testng.xml b/openapi-cli/src/test/resources/testng.xml index 3589aa05b..2912d0e70 100644 --- a/openapi-cli/src/test/resources/testng.xml +++ b/openapi-cli/src/test/resources/testng.xml @@ -115,6 +115,7 @@ under the License. + From b47e53c44a1a7c5b64e36e24dd659acb718303a1 Mon Sep 17 00:00:00 2001 From: lnash94 Date: Mon, 12 Aug 2024 15:51:08 +0530 Subject: [PATCH 2/3] Fix uninitialized field 'apiKeyConfig' issue --- .../generators/client/mock/reference_example.bal | 2 +- .../client/mock/AdvanceMockClientGenerator.java | 10 ++++++++++ .../client/mock/BallerinaMockClientGenerator.java | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/openapi-cli/src/test/resources/generators/client/mock/reference_example.bal b/openapi-cli/src/test/resources/generators/client/mock/reference_example.bal index 2ef4fd7d0..b46fc387d 100644 --- a/openapi-cli/src/test/resources/generators/client/mock/reference_example.bal +++ b/openapi-cli/src/test/resources/generators/client/mock/reference_example.bal @@ -1,5 +1,5 @@ public isolated client class Client { - final readonly & ApiKeysConfig apiKeyConfig; + final readonly & ApiKeysConfig apiKeyConfig = {apikey: ""}; # Gets invoked to initialize the `connector`. # # + apiKeyConfig - API keys for authorization diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/AdvanceMockClientGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/AdvanceMockClientGenerator.java index 8ea661804..1f2deb877 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/AdvanceMockClientGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/AdvanceMockClientGenerator.java @@ -20,6 +20,7 @@ import io.ballerina.compiler.syntax.tree.FunctionBodyNode; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NodeParser; import io.ballerina.compiler.syntax.tree.ObjectFieldNode; import io.ballerina.compiler.syntax.tree.ReturnStatementNode; import io.ballerina.compiler.syntax.tree.StatementNode; @@ -42,6 +43,7 @@ import static io.ballerina.compiler.syntax.tree.NodeFactory.createFunctionBodyBlockNode; import static io.ballerina.compiler.syntax.tree.NodeFactory.createReturnStatementNode; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_BRACE_TOKEN; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.EQUAL_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.OPEN_BRACE_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.RETURN_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.SEMICOLON_TOKEN; @@ -85,6 +87,14 @@ public List createClassInstanceVariables() { // add apiKey instance variable when API key security schema is given ObjectFieldNode apiKeyFieldNode = authConfigGeneratorImp.getApiKeyMapClassVariable(); if (apiKeyFieldNode != null) { + apiKeyFieldNode = apiKeyFieldNode.modify(apiKeyFieldNode.metadata().orElse(null), + apiKeyFieldNode.visibilityQualifier().orElse(null), + apiKeyFieldNode.qualifierList(), + apiKeyFieldNode.typeName(), + apiKeyFieldNode.fieldName(), + createToken(EQUAL_TOKEN), + NodeParser.parseExpression("{ apikey: \"\"}"), apiKeyFieldNode.semicolonToken()); + fieldNodeList.add(apiKeyFieldNode); } return fieldNodeList; diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/BallerinaMockClientGenerator.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/BallerinaMockClientGenerator.java index 1bae074a5..1a860db5a 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/BallerinaMockClientGenerator.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/client/mock/BallerinaMockClientGenerator.java @@ -25,6 +25,7 @@ import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NodeParser; import io.ballerina.compiler.syntax.tree.ObjectFieldNode; import io.ballerina.compiler.syntax.tree.ReturnStatementNode; import io.ballerina.compiler.syntax.tree.StatementNode; @@ -54,6 +55,7 @@ import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLASS_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLIENT_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLOSE_BRACE_TOKEN; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.EQUAL_TOKEN; import static io.ballerina.compiler.syntax.tree.SyntaxKind.FUNCTION_DEFINITION; import static io.ballerina.compiler.syntax.tree.SyntaxKind.FUNCTION_KEYWORD; import static io.ballerina.compiler.syntax.tree.SyntaxKind.ISOLATED_KEYWORD; @@ -127,6 +129,13 @@ public List createClassInstanceVariables() { // add apiKey instance variable when API key security schema is given ObjectFieldNode apiKeyFieldNode = authConfigGeneratorImp.getApiKeyMapClassVariable(); if (apiKeyFieldNode != null) { + apiKeyFieldNode = apiKeyFieldNode.modify(apiKeyFieldNode.metadata().orElse(null), + apiKeyFieldNode.visibilityQualifier().orElse(null), + apiKeyFieldNode.qualifierList(), + apiKeyFieldNode.typeName(), + apiKeyFieldNode.fieldName(), + createToken(EQUAL_TOKEN), + NodeParser.parseExpression("{ apikey: \"\"}"), apiKeyFieldNode.semicolonToken()); fieldNodeList.add(apiKeyFieldNode); } return fieldNodeList; From c309ee001d89a98337242de9d45288bca12426ab Mon Sep 17 00:00:00 2001 From: lnash94 Date: Mon, 12 Aug 2024 23:24:55 +0530 Subject: [PATCH 3/3] Fix parameter sanitized issue --- .../generators/openapi/ExampleTests.java | 2 +- .../generators/common/GeneratorConstants.java | 2 +- .../core/generators/common/OASModifier.java | 46 +++++++++++++------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java index 0269021b4..9dbbe2fbd 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/ExampleTests.java @@ -28,7 +28,7 @@ import java.nio.file.Paths; /** - * This test class for the covering the unit tests for return type scenarios. + * This test class for the covering the unit tests for example mapping scenarios. */ public class ExampleTests { private static final Path RES_DIR = Paths.get("src/test/resources/ballerina-to-openapi").toAbsolutePath(); diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java index 4900c9b5a..ab185bf08 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java @@ -89,7 +89,7 @@ public String getValue() { public static final String OAS_PATH_SEPARATOR = "/"; public static final String ESCAPE_PATTERN = "([\\[\\]\\\\?!<>@#&~'`*\\-=^+();:\\/{}\\s|.$])"; - public static final String ESCAPE_PATTERN_FOR_MODIFIER = "([\\[\\]\\\\?!<>@#&~'`*\\-=^+();:\\/{}\\s|.$]_)"; + public static final String ESCAPE_PATTERN_FOR_MODIFIER = "([\\[\\]\\\\?!<>@#&~'`*\\_\\-=^+();:\\/{}\\s|.$])"; public static final String REGEX_WITHOUT_SPECIAL_CHARACTERS = "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b"; public static final String REGEX_WORDS_STARTING_WITH_NUMBERS = "^[0-9].*"; public static final String REGEX_ONLY_NUMBERS_OR_NUMBERS_WITH_SPECIAL_CHARACTERS = "\\b[0-9([\\[\\]\\\\?!<>@#&~'`" + 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 ecfef1993..fe37f3627 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 @@ -275,11 +275,13 @@ private static String updateParameters(Map modifiedSchemas, Stri Optional modifiedPathParamResult = getValidNameForParameter(oasPathParamName); if (modifiedPathParamResult.isEmpty()) { //todo diagnostic, severity error + modifiedParameters.add(parameter); continue; } String modifiedPathParam = modifiedPathParamResult.get(); if (!modifiedPathParam.equals(oasPathParamName) && parameterNames.contains(modifiedPathParam)) { // todo: handle by adding abc_1 or abc_2 + modifiedParameters.add(parameter); continue; } // check given parameter has name which similar to component schema name @@ -289,8 +291,7 @@ private static String updateParameters(Map modifiedSchemas, Stri parameterNames.add(modifiedPathParam); parameter.setName(modifiedPathParam); modifiedParameters.add(parameter); - pathValue = pathValue.replace("{" + oasPathParamName + "}", "{" + - modifiedPathParam + "}"); + pathValue = replaceContentInBraces(pathValue, modifiedPathParam); } return pathValue; } @@ -491,10 +492,7 @@ private static RequestBody updateRequestBodySchemaType(String schemaName, String MediaType mediaType = stringMediaTypeEntry.getValue(); Schema requestSchemaType = mediaType.getSchema(); if (requestSchemaType != null) { - String ref = requestSchemaType.get$ref(); - if (ref != null) { - updateSchemaWithReference(schemaName, modifiedName, requestSchemaType); - } + updateSchemaWithReference(schemaName, modifiedName, requestSchemaType); mediaType.setSchema(requestSchemaType); } content.put(stringMediaTypeEntry.getKey(), mediaType); @@ -509,10 +507,7 @@ private static void updateParameterSchemaType(String schemaName, String modified for (Parameter parameter : operation.getParameters()) { if (parameter.getSchema() != null) { Schema paramSchema = parameter.getSchema(); - String ref = paramSchema.get$ref(); - if (ref != null) { - updateSchemaWithReference(schemaName, modifiedName, paramSchema); - } + updateSchemaWithReference(schemaName, modifiedName, paramSchema); parameter.setSchema(paramSchema); } modifiedParameters.add(parameter); @@ -553,14 +548,13 @@ public static Optional getValidNameForParameter(String identifier) { StringBuilder validName = new StringBuilder(); for (String part : split) { if (!part.isBlank()) { - if (split.length > 1) { part = part.substring(0, 1).toUpperCase(Locale.ENGLISH) + part.substring(1).toLowerCase(Locale.ENGLISH); - } validName.append(part); } } - identifier = validName.toString(); + String modifiedName = validName.substring(0, 1).toLowerCase(Locale.ENGLISH) + validName.substring(1); + identifier = split.length > 1 ? modifiedName : identifier; } else { identifier = "param" + identifier; } @@ -572,4 +566,30 @@ public static List collectParameterNames(List parameters) { .map(Parameter::getName) .collect(Collectors.toList()); } + + public static String replaceContentInBraces(String input, String expectedValue) { + String regex = "\\{([^}]*)\\}"; + + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + + StringBuilder result = new StringBuilder(); + + int lastEnd = 0; + while (matcher.find()) { + result.append(input, lastEnd, matcher.start()); + String content = getValidNameForParameter(matcher.group(1)).get(); + if (content.equals(expectedValue)) { + // Replace the content with the replacement + result.append("{").append(expectedValue).append("}"); + } else { + result.append(matcher.group()); + } + lastEnd = matcher.end(); + } + + // Append the remaining text after the last match + result.append(input.substring(lastEnd)); + return result.toString(); + } }