From b6f92b91afcce23a8d835ce2d44a6fdd348a0619 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 14:49:51 +0530 Subject: [PATCH 1/6] Add support for query annotation with name field --- .../openapi/service/mapper/Constants.java | 1 + .../mapper/parameter/QueryParameterMapper.java | 4 +++- .../mapper/utils/MapperCommonUtils.java | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java index 05a0d675e..178339430 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/Constants.java @@ -50,6 +50,7 @@ public final class Constants { public static final String ERROR_PAYLOAD = "ErrorPayload"; public static final String TREAT_NILABLE_AS_OPTIONAL = "treatNilableAsOptional"; public static final String HTTP_HEADER = "http:Header"; + public static final String HTTP_QUERY = "http:Query"; public static final String BYTE = "byte"; public static final String XML = "xml"; public static final String JSON = "json"; diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java index 36a7b34b2..8e6aceaad 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/parameter/QueryParameterMapper.java @@ -36,6 +36,7 @@ import java.util.Map; import java.util.Objects; +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.getQueryName; import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.unescapeIdentifier; /** @@ -62,7 +63,8 @@ public QueryParameterMapper(ParameterNode parameterNode, Map api Symbol parameterSymbol = additionalData.semanticModel().symbol(parameterNode).orElse(null); if (Objects.nonNull(parameterSymbol) && (parameterSymbol instanceof ParameterSymbol queryParameter)) { this.type = queryParameter.typeDescriptor(); - this.name = unescapeIdentifier(queryParameter.getName().get()); + String paramName = unescapeIdentifier(queryParameter.getName().get()); + this.name = getQueryName(parameterNode, paramName); this.isRequired = queryParameter.paramKind().equals(ParameterKind.REQUIRED); this.description = apiDocs.get(queryParameter.getName().get()); this.treatNilableAsOptional = treatNilableAsOptional; diff --git a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java index ea5cf09a7..a62bbad46 100644 --- a/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java +++ b/ballerina-to-openapi/src/main/java/io/ballerina/openapi/service/mapper/utils/MapperCommonUtils.java @@ -99,6 +99,8 @@ import static io.ballerina.openapi.service.mapper.Constants.BALLERINA; import static io.ballerina.openapi.service.mapper.Constants.EMPTY; import static io.ballerina.openapi.service.mapper.Constants.HTTP; +import static io.ballerina.openapi.service.mapper.Constants.HTTP_HEADER; +import static io.ballerina.openapi.service.mapper.Constants.HTTP_QUERY; import static io.ballerina.openapi.service.mapper.Constants.HTTP_SERVICE_CONTRACT; import static io.ballerina.openapi.service.mapper.Constants.HYPHEN; import static io.ballerina.openapi.service.mapper.Constants.JSON_EXTENSION; @@ -439,6 +441,14 @@ public static String unescapeIdentifier(String parameterName) { } public static String getHeaderName(ParameterNode parameterNode, String defaultName) { + return getParamName(HTTP_HEADER, parameterNode, defaultName); + } + + public static String getQueryName(ParameterNode parameterNode, String defaultName) { + return getParamName(HTTP_QUERY, parameterNode, defaultName); + } + + public static String getParamName(String paramTypeName, ParameterNode parameterNode, String defaultName) { NodeList annotations = null; if (parameterNode instanceof RequiredParameterNode requiredParameterNode) { annotations = requiredParameterNode.annotations(); @@ -449,9 +459,9 @@ public static String getHeaderName(ParameterNode parameterNode, String defaultNa return defaultName; } for (AnnotationNode annotation : annotations) { - if (annotation.annotReference().toString().trim().equals("http:Header")) { - String valueExpression = getNameFromHeaderAnnotation(annotation); - if (valueExpression != null) { + if (annotation.annotReference().toString().trim().equals(paramTypeName)) { + String valueExpression = getNameFromParamAnnotation(annotation); + if (Objects.nonNull(valueExpression)) { return valueExpression; } } @@ -459,7 +469,7 @@ public static String getHeaderName(ParameterNode parameterNode, String defaultNa return defaultName; } - private static String getNameFromHeaderAnnotation(AnnotationNode annotation) { + private static String getNameFromParamAnnotation(AnnotationNode annotation) { Optional annotationRecord = annotation.annotValue(); if (annotationRecord.isEmpty()) { return null; From 79d67411bf788cbbdcf65215a9314b14a0df205d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 14:58:43 +0530 Subject: [PATCH 2/6] Update spec --- docs/ballerina-to-oas/spec/spec.md | 43 +++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/ballerina-to-oas/spec/spec.md b/docs/ballerina-to-oas/spec/spec.md index ff3153c5c..e43465e0f 100644 --- a/docs/ballerina-to-oas/spec/spec.md +++ b/docs/ballerina-to-oas/spec/spec.md @@ -725,6 +725,26 @@ parameters: nullable: true ``` +The query parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `@http:Query` annotation. + +```ballerina +service /api on new http:Listener(9090) { + + resource function get path(@http:Query{name: "userName"} string param) { + // ... + } +} +``` + +```yml +parameters: + - name: userName + in: query + required: true + schema: + type: string +``` + If the query parameter type is a `map` or `record` with `anydata` fields, then the query parameter schema is wrapped with `content` and `application/json` to indicate that the query parameter should be a JSON object which should be encoded properly. ```ballerina @@ -836,6 +856,27 @@ parameters: nullable: true ``` +The header parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `@http:Header` annotation. + +```ballerina +service /api on new http:Listener(9090) { + + resource function get path(@http:Header{name: "xApiKeys"} string[] param) { + // ... + } +} +``` + +```yml +parameters: + - name: xApiKeys + in: header + schema: + type: array + items: + type: string +``` + Additionally, the header parameter can be a closed `record` which contains the above basic types as fields. In that case, each field of this record represents a header parameter. ```ballerina @@ -1029,7 +1070,7 @@ additionalProperties: false -string:Char +string:Char ```yml From bef2c86c0faa04d3845404aec5ac2c75e13bed73 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 18 Sep 2024 13:17:09 +0530 Subject: [PATCH 3/6] Update http version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c68ffb5d8..92406a7ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,7 +49,7 @@ stdlibJwtVersion=2.13.0 stdlibOAuth2Version=2.12.0 # Stdlib Level 05 -stdlibHttpVersion=2.12.0 +stdlibHttpVersion=2.12.1-20240918-130700-9906dbe # Stdlib Level 06 stdlibGrpcVersion=1.12.0 From b5863f33b423b2d9f0e4577ec8562c300bf3b800 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 18 Sep 2024 13:25:20 +0530 Subject: [PATCH 4/6] Add test case --- .../openapi/QueryParameterTests.java | 6 + .../expected_gen/query/query_scenario10.yaml | 174 ++++++++++++++++++ .../query/query_scenario10.bal | 31 ++++ 3 files changed, 211 insertions(+) create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/query/query_scenario10.yaml create mode 100644 openapi-cli/src/test/resources/ballerina-to-openapi/query/query_scenario10.bal diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/QueryParameterTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/QueryParameterTests.java index 0a5ead90c..0fc3c891e 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/QueryParameterTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/generators/openapi/QueryParameterTests.java @@ -98,6 +98,12 @@ public void testQueryscenario09() throws IOException { TestUtils.compareWithGeneratedFile(ballerinaFilePath, "query/query_scenario09.yaml"); } + @Test(description = "Query parameters configured with name field") + public void testQueryscenario10() throws IOException { + Path ballerinaFilePath = RES_DIR.resolve("query/query_scenario10.bal"); + TestUtils.compareWithGeneratedFile(ballerinaFilePath, "query/query_scenario10.yaml"); + } + @AfterMethod public void cleanUp() { TestUtils.deleteDirectory(this.tempDir); diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/query/query_scenario10.yaml b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/query/query_scenario10.yaml new file mode 100644 index 000000000..33a1e21bc --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/expected_gen/query/query_scenario10.yaml @@ -0,0 +1,174 @@ +openapi: 3.0.1 +info: + title: PayloadV + version: 0.0.0 +servers: + - url: "{server}:{port}/payloadV" + variables: + server: + default: http://localhost + port: + default: "9090" +paths: + /query: + get: + operationId: getQuery + parameters: + - name: query0 + in: query + schema: + type: string + default: "" + - name: q1 + in: query + schema: + type: string + - name: query2 + in: query + schema: + type: array + items: + type: string + default: [] + - name: query3 + in: query + schema: + type: array + items: + type: string + default: + - one + - two + - three + - name: query4 + in: query + schema: + type: array + items: + type: integer + format: int64 + default: + - 1 + - 2 + - 3 + - name: query5 + in: query + schema: + type: array + items: + type: number + format: float + default: + - 1 + - 2.3 + - 4.56 + - name: query6 + in: query + content: + application/json: + schema: + type: object + additionalProperties: + type: string + default: + name: John + city: London + - name: query7 + in: query + content: + application/json: + schema: + type: array + items: + type: object + additionalProperties: + type: string + default: + - name: John + age: "25" + - name: David + age: "30" + - name: query8 + in: query + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Record" + default: + name: John + address: + number: 14/7 + streetName: 2nd cross street + city: London + - name: query9 + in: query + content: + application/json: + schema: + type: object + additionalProperties: + type: number + format: float + default: {} + - name: query10 + in: query + schema: + type: array + items: + type: boolean + default: + - true + - false + - true + responses: + "200": + description: Ok + content: + text/plain: + schema: + type: string + "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 + Record: + required: + - address + - name + type: object + properties: + name: + type: string + address: + type: object + additionalProperties: + type: string + additionalProperties: false diff --git a/openapi-cli/src/test/resources/ballerina-to-openapi/query/query_scenario10.bal b/openapi-cli/src/test/resources/ballerina-to-openapi/query/query_scenario10.bal new file mode 100644 index 000000000..962fa817c --- /dev/null +++ b/openapi-cli/src/test/resources/ballerina-to-openapi/query/query_scenario10.bal @@ -0,0 +1,31 @@ +import ballerina/http; + +listener http:Listener helloEp = new (9090); + +type Record record {| + string name; + map address; +|}; + +const query1 = "query1"; + +service /payloadV on helloEp { + + resource function get query( + @http:Query{name: "query0"} string q0 = "", + @http:Query{name: query1} string q1 = string `"John"`, + @http:Query{name: "query2"} string[] q2 = [], + @http:Query{name: "query3"} string[] q3 = ["one", "two", "three"], + @http:Query{name: "query4"} int[] q4 = [1, 2, 3], + @http:Query{name: "query5"} float[] q5 = [1, 2.3, 4.56], + @http:Query{name: "query6"} map q6 = {"name": "John", "city": "London"}, + @http:Query{name: "query7"} map[] q7 = [{"name": "John", age: "25"}, + {name: "David", age: "30"}], + @http:Query{name: "query8" } Record q8 = {name: "John", address: {number: "14/7", + streetName: "2nd cross street", city: "London"}}, + @http:Query{name: "query9"} map q9 = {}, + @http:Query{name: "query10"} boolean[] q10 = [true, false, true] + ) returns string { + return "new"; + } +} From e57066b5363a7015bb3fcfc64aeda22e8423bbdc Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 18 Sep 2024 15:19:19 +0530 Subject: [PATCH 5/6] Fix test case --- .../project_openapi_bal_ext/result_1.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml index 0dafce369..996094380 100644 --- a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml @@ -57,7 +57,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Date DateFields: @@ -81,7 +81,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: DateFields OptionalTimeOfDayFields: @@ -100,7 +100,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: OptionalTimeOfDayFields Seconds: @@ -111,7 +111,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Seconds Student: @@ -165,6 +165,6 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: ZoneOffset From 9d88386ba932a402494a8e0c04687a0a1f3aeb56 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 09:29:57 +0530 Subject: [PATCH 6/6] Add suggestions from review --- docs/ballerina-to-oas/spec/spec.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ballerina-to-oas/spec/spec.md b/docs/ballerina-to-oas/spec/spec.md index e43465e0f..fca8d2f0b 100644 --- a/docs/ballerina-to-oas/spec/spec.md +++ b/docs/ballerina-to-oas/spec/spec.md @@ -725,7 +725,7 @@ parameters: nullable: true ``` -The query parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `@http:Query` annotation. +The query parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `name` attribute in the `@http:Query` annotation. ```ballerina service /api on new http:Listener(9090) { @@ -856,7 +856,7 @@ parameters: nullable: true ``` -The header parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `@http:Header` annotation. +The header parameter name in the schema is defaulted to the Ballerina parameter name. But this can be overridden using the `name` attribute in the `@http:Header` annotation. ```ballerina service /api on new http:Listener(9090) {