diff --git a/ballerina-tests/http-dispatching-tests/tests/service_dispatching_query_param_binding_test.bal b/ballerina-tests/http-dispatching-tests/tests/service_dispatching_query_param_binding_test.bal index 1ce2f6b41..c442d6ec0 100644 --- a/ballerina-tests/http-dispatching-tests/tests/service_dispatching_query_param_binding_test.bal +++ b/ballerina-tests/http-dispatching-tests/tests/service_dispatching_query_param_binding_test.bal @@ -15,9 +15,9 @@ // under the License. import ballerina/http; +import ballerina/http_test_common as common; import ballerina/test; import ballerina/url; -import ballerina/http_test_common as common; listener http:Listener QueryBindingEP = new (queryParamBindingTestPort, httpVersion = http:HTTP_1_1); final http:Client queryBindingClient = check new ("http://localhost:" + queryParamBindingTestPort.toString(), httpVersion = http:HTTP_1_1); @@ -167,8 +167,29 @@ service /queryparamservice on QueryBindingEP { resource function get studentRest(StudentRest studentRest) returns StudentRest { return studentRest; } + + resource function get queryAnnotation(@http:Query {name: "first"} string firstName, @http:Query {name: "last-name"} string lastName) returns string { + return string `Hello, ${firstName} ${lastName}`; + } + + resource function get queryAnnotation/negative/q1(@http:Query {name: ""} string firstName) returns string { + return string `Hello, ${firstName}`; + } + + resource function get queryAnnotation/negative/q2(@http:Query {name: ()} string lastName) returns string { + return string `Hello, ${lastName}`; + } + + resource function get queryAnnotation/mapQueries(@http:Query {name: "rMq"} Mq mq) returns string { + return string `Hello, ${mq.name} ${mq.age}`; + } } +public type Mq record { + string name; + int age; +}; + service /default on QueryBindingEP { resource function get checkstring(string foo = "hello") returns json { json responseJson = {value1: foo}; @@ -353,7 +374,7 @@ function testNegativeStringQueryBindingCaseSensitivity() returns error? { if response is http:Response { test:assertEquals(response.statusCode, 400); check common:assertJsonErrorPayload(check response.getJsonPayload(), "no query param value found for 'foo'", - "Bad Request", 400, "/queryparamservice/?FOO=WSO2&bar=go", "GET"); + "Bad Request", 400, "/queryparamservice/?FOO=WSO2&bar=go", "GET"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -365,7 +386,7 @@ function testNegativeIntQueryBindingCastingError() returns error? { if response is http:Response { test:assertEquals(response.statusCode, 400); check common:assertJsonErrorPayload(check response.getJsonPayload(), "error in casting query param : 'bar'", - "Bad Request", 400, "/queryparamservice/?foo=WSO2&bar=go", "GET"); + "Bad Request", 400, "/queryparamservice/?foo=WSO2&bar=go", "GET"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -374,7 +395,7 @@ function testNegativeIntQueryBindingCastingError() returns error? { if response is http:Response { test:assertEquals(response.statusCode, 400); check common:assertJsonErrorPayload(check response.getJsonPayload(), "error in casting query param : 'bar'", - "Bad Request", 400, "/queryparamservice/?foo=WSO2&bar=", "GET"); + "Bad Request", 400, "/queryparamservice/?foo=WSO2&bar=", "GET"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -549,7 +570,7 @@ function testEmptyQueryParamBinding() returns error? { if response is http:Response { test:assertEquals(response.statusCode, 400); check common:assertJsonErrorPayload(check response.getJsonPayload(), "no query param value found for 'x-Type'", - "Bad Request", 400, "/queryparamservice/q9?x-Type", "GET"); + "Bad Request", 400, "/queryparamservice/q9?x-Type", "GET"); } else { test:assertFail(msg = "Found unexpected output type: " + response.message()); } @@ -736,3 +757,27 @@ function testMapOfJsonTypedQueryParamBinding3() returns error? { response = check queryBindingClient->get("/queryparamservice/q13?obj=" + mapOfJsonsEncoded); test:assertEquals(response.statusCode, 400, msg = "Found unexpected output"); } + +@test:Config {} +function testforQueryParamterNameOverwrite() returns error? { + string result = check queryBindingClient->get("/queryparamservice/queryAnnotation?first=Harry&last-name=Potter"); + test:assertEquals(result, "Hello, Harry Potter", msg = string `Found ${result}, expected Harry`); + + map mapOfJsons = { + name: "Ron", + age: 10 + }; + string mapOfQueries = check url:encode(mapOfJsons.toJsonString(), "UTF-8"); + + result = check queryBindingClient->get("/queryparamservice/queryAnnotation/mapQueries?rMq=" + mapOfQueries); + test:assertEquals(result, "Hello, Ron 10", msg = string `Found ${result}, expected Harry`); +} + +@test:Config {} +function testforNegativeQueryParamterNameOverwrite() returns error? { + string result = check queryBindingClient->get("/queryparamservice/queryAnnotation/negative/q1?firstName=Harry"); + test:assertEquals(result, "Hello, Harry"); + + result = check queryBindingClient->get("/queryparamservice/queryAnnotation/negative/q2?lastName=Anne"); + test:assertEquals(result, "Hello, Anne"); +} diff --git a/changelog.md b/changelog.md index 83dd8125f..e86f17166 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Add `anydata` support for `setPayload` methods in the request and response objects](https://github.com/ballerina-platform/ballerina-library/issues/6954) - [Improve `@http:Query` annotation to overwrite the query parameter name in client] (https://github.com/ballerina-platform/ballerina-library/issues/6983) +- [Improve `@http:Query` annotation to overwrite the query parameter name in service] (https://github.com/ballerina-platform/ballerina-library/issues/7006) ## [2.12.0] - 2024-08-20 diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java index 2bca7fdd5..213d80318 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java @@ -47,6 +47,7 @@ import static io.ballerina.stdlib.http.api.HttpConstants.ANN_NAME_CALLER_INFO; import static io.ballerina.stdlib.http.api.HttpConstants.ANN_NAME_HEADER; import static io.ballerina.stdlib.http.api.HttpConstants.ANN_NAME_PAYLOAD; +import static io.ballerina.stdlib.http.api.HttpConstants.ANN_NAME_QUERY; import static io.ballerina.stdlib.http.api.HttpConstants.COLON; import static io.ballerina.stdlib.http.api.HttpConstants.PROTOCOL_HTTP; import static io.ballerina.stdlib.http.api.HttpUtil.getParameterTypes; @@ -89,6 +90,7 @@ public class ParamHandler { + ANN_NAME_CALLER_INFO; public static final String CACHE_ANNOTATION = ModuleUtils.getHttpPackageIdentifier() + COLON + ANN_NAME_CACHE; + public static final String QUERY_ANNOTATION = ModuleUtils.getHttpPackageIdentifier() + COLON + ANN_NAME_QUERY; public ParamHandler(ResourceMethodType resource, int pathParamCount, boolean constraintValidation) { this.resource = resource; @@ -272,11 +274,28 @@ private void createHeaderParam(String paramName, BMap annotations) { private void createQueryParam(int index, ResourceMethodType balResource, Type originalType) { io.ballerina.runtime.api.types.Parameter parameter = balResource.getParameters()[index]; - QueryParam queryParam = new QueryParam(originalType, HttpUtil.unescapeAndEncodeValue(parameter.name), index, - parameter.isDefault, constraintValidation); + String paramName = parameter.name; + BMap annotations = (BMap) balResource.getAnnotation( + StringUtils.fromString(PARAM_ANNOT_PREFIX + IdentifierUtils.escapeSpecialCharacters(paramName))); + if (annotations != null) { + String queryParamName = getQueryParamName(paramName, annotations); + paramName = queryParamName.isBlank() ? paramName : queryParamName; + } + paramName = HttpUtil.unescapeAndEncodeValue(paramName); + QueryParam queryParam = new QueryParam(originalType, paramName, index, parameter.isDefault, + constraintValidation); this.queryParams.add(queryParam); } + private static String getQueryParamName(String paramName, BMap annotations) { + BMap mapValue = annotations.getMapValue(StringUtils.fromString(QUERY_ANNOTATION)); + Object queryName = mapValue.get(HttpConstants.ANN_FIELD_NAME); + if (queryName instanceof BString query) { + return query.getValue(); + } + return paramName; + } + public boolean isPayloadBindingRequired() { return payloadParam != null; }