From 4c8834f287791f27ae5b30762768b0ec7cf80ed5 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Mon, 11 Nov 2024 09:47:31 +0530 Subject: [PATCH 01/13] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 2 +- ballerina/Dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 10736d50e..05eccd9f2 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -7,7 +7,7 @@ keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.10.0" +distribution = "2201.11.0" export = ["http", "http.httpscerr"] [platform.java17] diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 9f60e78d3..7eeadfd35 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -328,7 +328,7 @@ dependencies = [ [[package]] org = "ballerina" name = "time" -version = "2.5.0" +version = "2.4.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From f55c3e741ef7ce6f6cc33eb1ac312d8085325b2e Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Mon, 11 Nov 2024 09:58:39 +0530 Subject: [PATCH 02/13] [Automated] Update the native jar versions --- ballerina-tests/http-service-tests/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 4efebe9cc..eaa2f208f 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0-20241008-112400-81975006" [[package]] org = "ballerina" From c3d118b44639a6446658a0689dbe63f7618069c0 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Fri, 22 Nov 2024 09:18:54 +0530 Subject: [PATCH 03/13] Add support for relaxed data binding --- ballerina/http_annotation.bal | 2 + ballerina/http_client_endpoint.bal | 33 +++++++------- ballerina/http_client_payload_builder.bal | 44 ++++++++++++------- ballerina/http_response.bal | 8 ++-- ballerina/http_status_code_client.bal | 19 ++++---- ballerina/http_types.bal | 2 + ballerina/resiliency_failover_client.bal | 19 ++++---- ballerina/resiliency_load_balance_client.bal | 19 ++++---- .../stdlib/http/api/HttpResource.java | 10 +++++ .../stdlib/http/api/HttpService.java | 11 +++++ .../nativeimpl/ExternResponseProcessor.java | 21 ++++----- 11 files changed, 119 insertions(+), 69 deletions(-) diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index 0de46cc30..eab45740f 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -27,6 +27,7 @@ # + validation - Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default # + serviceType - The service object type which defines the service contract. This is auto-generated at compile-time # + basePath - Base path to be used with the service implementation. This is only allowed on service contract types +# + laxDataBinding - Enables or disalbles relaxed data binding on the service side. Disabled by default public type HttpServiceConfig record {| string host = "b7a.default"; CompressionConfig compression = {}; @@ -39,6 +40,7 @@ public type HttpServiceConfig record {| boolean validation = true; typedesc serviceType?; string basePath?; + boolean laxDataBinding = false; |}; # Configurations for CORS support. diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 8b74380b5..93551ee4c 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -28,6 +28,7 @@ import ballerina/time; # HTTP service in resilient manner # + cookieStore - Stores the cookies of the client # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package +# + requireLaxDataBinding - Enables or disalbles relaxed data binding. public client isolated class Client { *ClientObject; @@ -35,6 +36,7 @@ public client isolated class Client { private CookieStore? cookieStore = (); final HttpClient httpClient; private final boolean requireValidation; + private final boolean requireLaxDataBinding; # Gets invoked to initialize the `client`. During initialization, the configurations provided through the `config` # record is used to determine which type of additional behaviours are added to the endpoint (e.g., caching, @@ -53,6 +55,7 @@ public client isolated class Client { } self.httpClient = check initialize(url, config, self.cookieStore); self.requireValidation = config.validation; + self.requireLaxDataBinding = config.laxDataBinding; return; } @@ -95,7 +98,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_POST, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP PUT requests to HTTP endpoints. @@ -137,7 +140,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PUT, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP PATCH requests to HTTP endpoints. @@ -179,7 +182,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PATCH, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP DELETE requests to HTTP endpoints. @@ -221,7 +224,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_DELETE, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP HEAD requests to HTTP endpoints. @@ -283,7 +286,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_GET, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. @@ -319,7 +322,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_OPTIONS, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # Invokes an HTTP call with the specified HTTP verb. @@ -347,7 +350,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, httpVerb, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb @@ -368,7 +371,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, request.method, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # Submits an HTTP request to a service with the specified HTTP verb. @@ -720,7 +723,7 @@ isolated function createStatusCodeResponseDataBindingError(DataBindingErrorType } } -isolated function processResponse(Response|ClientError response, TargetType targetType, boolean requireValidation) +isolated function processResponse(Response|ClientError response, TargetType targetType, boolean requireValidation, boolean requireLaxDataBinding) returns Response|stream|anydata|ClientError { if response is ClientError || hasHttpResponseType(targetType) { return response; @@ -741,7 +744,7 @@ isolated function processResponse(Response|ClientError response, TargetType targ } } if targetType is typedesc { - anydata payload = check performDataBinding(response, targetType); + anydata payload = check performDataBinding(response, targetType, requireLaxDataBinding); if requireValidation { return performDataValidation(payload, targetType); } @@ -773,21 +776,21 @@ isolated function validateEventStreamContentType(Response response) returns Clie } } -isolated function processResponseNew(Response|ClientError response, typedesc targetType, boolean requireValidation) - returns StatusCodeResponse|ClientError { +isolated function processResponseNew(Response|ClientError response, typedesc targetType, boolean requireValidation, + boolean requireLaxDataBinding) returns StatusCodeResponse|ClientError { if response is ClientError { return response; } - return externProcessResponseNew(response, targetType, requireValidation); + return externProcessResponseNew(response, targetType, requireValidation, requireLaxDataBinding); } -isolated function externProcessResponse(Response response, TargetType targetType, boolean requireValidation) +isolated function externProcessResponse(Response response, TargetType targetType, boolean requireValidation, boolean requireLaxDataBinding) returns Response|anydata|StatusCodeResponse|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternResponseProcessor", name: "processResponse" } external; -isolated function externProcessResponseNew(Response response, typedesc targetType, boolean requireValidation) +isolated function externProcessResponseNew(Response response, typedesc targetType, boolean requireValidation, boolean requireLaxDataBinding) returns StatusCodeResponse|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternResponseProcessor", name: "processResponse" diff --git a/ballerina/http_client_payload_builder.bal b/ballerina/http_client_payload_builder.bal index ca4f012e7..f5ca177b8 100644 --- a/ballerina/http_client_payload_builder.bal +++ b/ballerina/http_client_payload_builder.bal @@ -25,10 +25,10 @@ type stringType typedesc; type byteArrType typedesc; type mapStringType typedesc>; -isolated function performDataBinding(Response response, TargetType targetType) returns anydata|ClientError { +isolated function performDataBinding(Response response, TargetType targetType, boolean requireLaxDataBinding) returns anydata|ClientError { string contentType = response.getContentType().trim(); if contentType == "" { - return getBuilderFromType(response, targetType); + return getBuilderFromType(response, targetType, requireLaxDataBinding); } if XML_PATTERN.isFullMatch(contentType) { return xmlPayloadBuilder(response, targetType); @@ -39,13 +39,13 @@ isolated function performDataBinding(Response response, TargetType targetType) r } else if OCTET_STREAM_PATTERN.isFullMatch(contentType) { return blobPayloadBuilder(response, targetType); } else if JSON_PATTERN.isFullMatch(contentType) { - return jsonPayloadBuilder(response, targetType); + return jsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else { - return getBuilderFromType(response, targetType); + return getBuilderFromType(response, targetType, requireLaxDataBinding); } } -isolated function getBuilderFromType(Response response, TargetType targetType) returns anydata|ClientError { +isolated function getBuilderFromType(Response response, TargetType targetType, boolean requireLaxDataBinding) returns anydata|ClientError { if targetType is typedesc { return response.getTextPayload(); } else if targetType is typedesc { @@ -67,7 +67,7 @@ isolated function getBuilderFromType(Response response, TargetType targetType) r } else { // Due to the limitation of https://github.com/ballerina-platform/ballerina-spec/issues/1090 // all the other types including union are considered as json subtypes. - return jsonPayloadBuilder(response, targetType); + return jsonPayloadBuilder(response, targetType, requireLaxDataBinding); } } @@ -135,20 +135,20 @@ isolated function blobPayloadBuilder(Response response, TargetType targetType) r } } -isolated function jsonPayloadBuilder(Response response, TargetType targetType) returns anydata|ClientError { +isolated function jsonPayloadBuilder(Response response, TargetType targetType, boolean requireLaxDataBinding) returns anydata|ClientError { if targetType is typedesc { - return nonNilablejsonPayloadBuilder(response, targetType); + return nonNilablejsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else if targetType is typedesc { - return nilablejsonPayloadBuilder(response, targetType); + return nilablejsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else if targetType is typedesc { - return nonNilablejsonPayloadBuilder(response, targetType); + return nonNilablejsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else if targetType is typedesc { - return nilablejsonPayloadBuilder(response, targetType); + return nilablejsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else if targetType is typedesc> { json payload = check response.getJsonPayload(); return > payload; } else if targetType is typedesc { - return nilablejsonPayloadBuilder(response, targetType); + return nilablejsonPayloadBuilder(response, targetType, requireLaxDataBinding); } else { // Consume payload to avoid memory leaks byte[]|ClientError payload = response.getBinaryPayload(); @@ -159,18 +159,30 @@ isolated function jsonPayloadBuilder(Response response, TargetType targetType) r } } -isolated function nonNilablejsonPayloadBuilder(Response response, typedesc targetType) +isolated function nonNilablejsonPayloadBuilder(Response response, typedesc targetType, boolean requireLaxDataBinding) returns anydata|ClientError { json payload = check response.getJsonPayload(); - var result = jsondata:parseAsType(payload, {enableConstraintValidation: false, allowDataProjection: false}, targetType); + var result = jsondata:parseAsType( + payload, + requireLaxDataBinding + ? {allowDataProjection: {nilAsOptionalField: true, absentAsNilableType: true}, enableConstraintValidation: false} + : {enableConstraintValidation: false, allowDataProjection: false}, + targetType + ); return result is error ? createPayloadBindingError(result) : result; } -isolated function nilablejsonPayloadBuilder(Response response, typedesc targetType) +isolated function nilablejsonPayloadBuilder(Response response, typedesc targetType, boolean requireLaxDataBinding) returns anydata|ClientError { json|ClientError payload = response.getJsonPayload(); if payload is json { - var result = jsondata:parseAsType(payload, {enableConstraintValidation: false, allowDataProjection: false}, targetType); + var result = jsondata:parseAsType( + payload, + requireLaxDataBinding + ? {allowDataProjection: {nilAsOptionalField: true, absentAsNilableType: true}, enableConstraintValidation: false} + : {enableConstraintValidation: false, allowDataProjection: false}, + targetType + ); return result is error ? createPayloadBindingError(result) : result; } else { return payload is NoContentError ? () : payload; diff --git a/ballerina/http_response.bal b/ballerina/http_response.bal index 3cb39c711..ff9a780ac 100644 --- a/ballerina/http_response.bal +++ b/ballerina/http_response.bal @@ -586,10 +586,10 @@ public class Response { } isolated function buildStatusCodeResponse(typedesc? payloadType, typedesc statusCodeResType, - boolean requireValidation, Status status, map headers, string? mediaType, + boolean requireValidation, boolean requireLaxDataBinding, Status status, map headers, string? mediaType, boolean fromDefaultStatusCodeMapping) returns StatusCodeResponse|ClientError { if payloadType !is () { - anydata|ClientError payload = self.performDataBinding(payloadType, requireValidation); + anydata|ClientError payload = self.performDataBinding(payloadType, requireValidation, requireLaxDataBinding); if payload is ClientError { return self.getStatusCodeResponseDataBindingError(payload.message(), fromDefaultStatusCodeMapping, PAYLOAD, payload); } @@ -599,8 +599,8 @@ public class Response { } } - isolated function performDataBinding(typedesc targetType, boolean requireValidation) returns anydata|ClientError { - anydata payload = check performDataBinding(self, targetType); + isolated function performDataBinding(typedesc targetType, boolean requireValidation, boolean requireLaxDataBinding) returns anydata|ClientError { + anydata payload = check performDataBinding(self, targetType, requireLaxDataBinding); if requireValidation { return performDataValidation(payload, targetType); } diff --git a/ballerina/http_status_code_client.bal b/ballerina/http_status_code_client.bal index f79e6371e..a701ee28b 100644 --- a/ballerina/http_status_code_client.bal +++ b/ballerina/http_status_code_client.bal @@ -26,6 +26,7 @@ import ballerina/observe; # HTTP service in resilient manner # + cookieStore - Stores the cookies of the client # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package +# + requireLaxDataBinding - Enables or disalbles relaxed data binding on the client side. public client isolated class StatusCodeClient { *StatusCodeClientObject; @@ -33,6 +34,7 @@ public client isolated class StatusCodeClient { private CookieStore? cookieStore = (); final HttpClient httpClient; private final boolean requireValidation; + private final boolean requireLaxDataBinding; # Gets invoked to initialize the `client`. During initialization, the configurations provided through the `config` # record is used to determine which type of additional behaviours are added to the endpoint (e.g., caching, @@ -51,6 +53,7 @@ public client isolated class StatusCodeClient { } self.httpClient = check initialize(url, config, self.cookieStore); self.requireValidation = config.validation; + self.requireLaxDataBinding = config.laxDataBinding; return; } @@ -93,7 +96,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_POST, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP PUT requests to HTTP endpoints. @@ -135,7 +138,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PUT, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP PATCH requests to HTTP endpoints. @@ -177,7 +180,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PATCH, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP DELETE requests to HTTP endpoints. @@ -219,7 +222,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_DELETE, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP HEAD requests to HTTP endpoints. @@ -281,7 +284,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_GET, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. @@ -317,7 +320,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_OPTIONS, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # Invokes an HTTP call with the specified HTTP verb. @@ -345,7 +348,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, httpVerb, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb @@ -366,7 +369,7 @@ public client isolated class StatusCodeClient { if observabilityEnabled && response is Response { addObservabilityInformation(path, request.method, response.statusCode, self.url); } - return processResponseNew(response, targetType, self.requireValidation); + return processResponseNew(response, targetType, self.requireValidation, self.requireLaxDataBinding); } # Submits an HTTP request to a service with the specified HTTP verb. diff --git a/ballerina/http_types.bal b/ballerina/http_types.bal index a96b48ec4..e82358098 100644 --- a/ballerina/http_types.bal +++ b/ballerina/http_types.bal @@ -105,6 +105,8 @@ public type CommonClientConfiguration record {| boolean validation = true; # Provides settings related to client socket configuration ClientSocketConfig socketConfig = {}; + # Enables or disalbles relaxed data binding on the client side. Disabled by default + boolean laxDataBinding = false; |}; # Represents a server-provided hyperlink diff --git a/ballerina/resiliency_failover_client.bal b/ballerina/resiliency_failover_client.bal index fe3aaaa4d..2bde2a49a 100644 --- a/ballerina/resiliency_failover_client.bal +++ b/ballerina/resiliency_failover_client.bal @@ -33,6 +33,7 @@ type FailoverInferredConfig record {| # + failoverInferredConfig - Configurations derived from `FailoverConfig` # + failoverClientsArray - Array of `Client` for target endpoints # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package +# + requireLaxDataBinding - Enables or disalbles relaxed data binding. public client isolated class FailoverClient { *ClientObject; @@ -40,6 +41,7 @@ public client isolated class FailoverClient { private final FailoverInferredConfig & readonly failoverInferredConfig; private final Client?[] failoverClientsArray; private final boolean requireValidation; + private final boolean requireLaxDataBinding; # Failover caller actions which provides failover capabilities to an HTTP client endpoint. # @@ -64,6 +66,7 @@ public client isolated class FailoverClient { }; self.failoverInferredConfig = failoverInferredConfig.cloneReadOnly(); self.requireValidation = failoverClientConfig.validation; + self.requireLaxDataBinding = failoverClientConfig.laxDataBinding; return; } @@ -106,7 +109,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -151,7 +154,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -196,7 +199,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -241,7 +244,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -309,7 +312,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -348,7 +351,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -379,7 +382,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } @@ -403,7 +406,7 @@ public client isolated class FailoverClient { if result is HttpFuture { return getInvalidTypeError(); } else if result is ClientError|Response { - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } else { panic error ClientError("invalid response type received"); } diff --git a/ballerina/resiliency_load_balance_client.bal b/ballerina/resiliency_load_balance_client.bal index 4e1612b1a..acd4b4b45 100644 --- a/ballerina/resiliency_load_balance_client.bal +++ b/ballerina/resiliency_load_balance_client.bal @@ -24,6 +24,7 @@ import ballerina/log; # + lbRule - Load balancing rule # + failover - Whether to fail over in case of a failure # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package +# + requireLaxDataBinding - Enables or disalbles relaxed data binding. public client isolated class LoadBalanceClient { *ClientObject; @@ -31,6 +32,7 @@ public client isolated class LoadBalanceClient { private LoadBalancerRule lbRule; private final boolean failover; private final boolean requireValidation; + private final boolean requireLaxDataBinding; # Load Balancer adds an additional layer to the HTTP client to make network interactions more resilient. # @@ -57,6 +59,7 @@ public client isolated class LoadBalanceClient { self.lbRule = loadBalancerRoundRobinRule; } self.requireValidation = loadBalanceClientConfig.validation; + self.requireLaxDataBinding = loadBalanceClientConfig.laxDataBinding; return; } @@ -96,7 +99,7 @@ public client isolated class LoadBalanceClient { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_POST); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The PUT resource function implementation of the LoadBalancer Connector. @@ -135,7 +138,7 @@ public client isolated class LoadBalanceClient { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PUT); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The PATCH resource function implementation of the LoadBalancer Connector. @@ -174,7 +177,7 @@ public client isolated class LoadBalanceClient { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PATCH); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The DELETE resource function implementation of the LoadBalancer Connector. @@ -213,7 +216,7 @@ public client isolated class LoadBalanceClient { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_DELETE); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The HEAD resource function implementation of the LoadBalancer Connector. @@ -268,7 +271,7 @@ public client isolated class LoadBalanceClient { returns Response|stream|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_GET); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The OPTIONS resource function implementation of the LoadBalancer Connector. @@ -301,7 +304,7 @@ public client isolated class LoadBalanceClient { returns Response|stream|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_OPTIONS); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The EXECUTE remote function implementation of the LoadBalancer Connector. @@ -326,7 +329,7 @@ public client isolated class LoadBalanceClient { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceExecuteAction(path, req, httpVerb); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The FORWARD remote function implementation of the LoadBalancer Connector. @@ -344,7 +347,7 @@ public client isolated class LoadBalanceClient { private isolated function processForward(string path, Request request, TargetType targetType) returns Response|stream|anydata|ClientError { var result = self.performLoadBalanceAction(path, request, HTTP_FORWARD); - return processResponse(result, targetType, self.requireValidation); + return processResponse(result, targetType, self.requireValidation, self.requireLaxDataBinding); } # The submit implementation of the LoadBalancer Connector. diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java index 0f8fd0bd0..de4d1597f 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java @@ -111,6 +111,7 @@ public class HttpResource implements Resource { private BMap cacheConfig; private boolean treatNilableAsOptional; private boolean constraintValidation; + private boolean laxDataBinding; protected HttpResource(MethodType resource, HttpService parentService) { this.balResource = resource; @@ -310,6 +311,7 @@ public static HttpResource buildHttpResource(MethodType resource, HttpService ht } processResourceCors(httpResource, httpService); httpResource.setConstraintValidation(httpService.getConstraintValidation()); + httpResource.setLaxDataBinding(httpService.getLaxDataBinding()); httpResource.prepareAndValidateSignatureParams(); if (Objects.nonNull(httpResource.getResourceLinkName()) && httpResource.linkReturnMediaTypes.isEmpty()) { Type resourceReturnType = httpResource.getBalResource().getType().getReturnType(); @@ -331,6 +333,14 @@ private boolean getConstraintValidation() { return this.constraintValidation; } + private void setLaxDataBinding(boolean laxDataBinding) { + this.laxDataBinding = laxDataBinding; + } + + private boolean getLaxDataBinding() { + return this.laxDataBinding; + } + private void updateLinkedResources(Object[] links) { for (Object link : links) { BMap linkMap = (BMap) link; diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index 7fbf32322..1f5d53c61 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -80,6 +80,7 @@ public class HttpService implements Service { private static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); private static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); private static final BString DATA_VALIDATION = fromString("validation"); + private static final BString LAX_DATA_BINDING = fromString("laxDataBinding"); private BObject balService; private List resources; @@ -98,6 +99,7 @@ public class HttpService implements Service { private BArray balInterceptorServicesArray; private byte[] introspectionPayload = new byte[0]; private Boolean constraintValidation = true; + private Boolean laxDataBinding; protected HttpService(BObject service, String basePath) { this.balService = service; @@ -262,6 +264,7 @@ protected void populateServiceConfig(BMap serviceConfig) { } this.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); this.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); + this.setLaxDataBinding(serviceConfig.getBooleanValue(LAX_DATA_BINDING)); } else { this.setHostName(HttpConstants.DEFAULT_HOST); } @@ -590,4 +593,12 @@ public boolean getConstraintValidation() { protected void setConstraintValidation(boolean constraintValidation) { this.constraintValidation = constraintValidation; } + + public boolean getLaxDataBinding() { + return laxDataBinding; + } + + protected void setLaxDataBinding(boolean laxDataBinding) { + this.laxDataBinding = laxDataBinding; + } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java index ac590a6d6..c258288a6 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java @@ -170,8 +170,8 @@ private ExternResponseProcessor() { } public static Object processResponse(Environment env, BObject response, BTypedesc targetType, - boolean requireValidation) { - return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env); + boolean requireValidation, boolean requireLaxDataBinding) { + return getResponseWithType(response, targetType.getDescribingType(), requireValidation, requireLaxDataBinding, env); } public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDesc, BObject status, BMap headers, @@ -192,7 +192,7 @@ public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDes } private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, - Environment env) { + boolean requireLaxDataBinding, Environment env) { long responseStatusCode = getStatusCode(response); // Find the most specific status code record type @@ -206,7 +206,7 @@ private static Object getResponseWithType(BObject response, Type targetType, boo if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { try { - return generateStatusCodeResponseType(response, requireValidation, env, statusCodeRecordType, + return generateStatusCodeResponseType(response, requireValidation, requireLaxDataBinding, env, statusCodeRecordType, responseStatusCode); } catch (StatusCodeBindingException exp) { return getStatusCodeResponseDataBindingError(env, response, exp.getMessage(), exp.getBError(), @@ -222,8 +222,8 @@ private static long getStatusCode(BObject response) { return response.getIntValue(StringUtils.fromString(STATUS_CODE)); } - private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Environment env, - RecordType statusCodeRecordType, long responseStatusCode) + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, boolean requireLaxDataBinding, + Environment env, RecordType statusCodeRecordType, long responseStatusCode) throws StatusCodeBindingException { String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { @@ -252,15 +252,15 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { payloadType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_BODY_FIELD).getFieldType(); } - Object[] paramFeed = getParamFeedForStatusCodeBinding(requireValidation, statusCodeRecordType, payloadType, - status, headers, mediaType); + Object[] paramFeed = getParamFeedForStatusCodeBinding(requireValidation, statusCodeRecordType, + payloadType, status, headers, mediaType, requireLaxDataBinding); return getStatusCodeResponse(env, response, paramFeed); } private static Object[] getParamFeedForStatusCodeBinding(boolean requireValidation, RecordType statusCodeType, Type payloadType, Object status, Object headers, - Object mediaType) { - Object[] paramFeed = new Object[14]; + Object mediaType, boolean requireLaxDataBinding) { + Object[] paramFeed = new Object[15]; paramFeed[0] = Objects.isNull(payloadType) ? null : ValueCreator.createTypedescValue(payloadType); paramFeed[1] = true; paramFeed[2] = ValueCreator.createTypedescValue(statusCodeType); @@ -275,6 +275,7 @@ private static Object[] getParamFeedForStatusCodeBinding(boolean requireValidati paramFeed[11] = true; paramFeed[12] = isDefaultStatusCodeResponseType(statusCodeType); paramFeed[13] = true; + paramFeed[14] = requireLaxDataBinding; return paramFeed; } From 49ce8cc76b6ffb48001f4b4723f81eb9cd8045dc Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Mon, 25 Nov 2024 16:38:29 +0530 Subject: [PATCH 04/13] Resolve merge conflicts --- .../nativeimpl/ExternResponseProcessor.java | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java index ef0d92108..ff1d8184b 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java @@ -263,31 +263,6 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r return createHttpError(STATUS_CODE_RES_CREATION_FAILED, STATUS_CODE_RESPONSE_BINDING_ERROR, ErrorCreator.createError(throwable)); } - Object[] paramFeed = getParamFeedForStatusCodeBinding(requireValidation, statusCodeRecordType, - payloadType, status, headers, mediaType, requireLaxDataBinding); - return getStatusCodeResponse(env, response, paramFeed); - } - - private static Object[] getParamFeedForStatusCodeBinding(boolean requireValidation, RecordType statusCodeType, - Type payloadType, Object status, Object headers, - Object mediaType, boolean requireLaxDataBinding) { - Object[] paramFeed = new Object[15]; - paramFeed[0] = Objects.isNull(payloadType) ? null : ValueCreator.createTypedescValue(payloadType); - paramFeed[1] = true; - paramFeed[2] = ValueCreator.createTypedescValue(statusCodeType); - paramFeed[3] = true; - paramFeed[4] = requireValidation; - paramFeed[5] = true; - paramFeed[6] = status; - paramFeed[7] = true; - paramFeed[8] = headers; - paramFeed[9] = true; - paramFeed[10] = mediaType; - paramFeed[11] = true; - paramFeed[12] = isDefaultStatusCodeResponseType(statusCodeType); - paramFeed[13] = true; - paramFeed[14] = requireLaxDataBinding; - return paramFeed; } private static Object getHeaders(BObject response, boolean requireValidation, RecordType statusCodeRecordType) { From d78f4584f4ff1a60742fec39517fb689f8f11ed3 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 08:44:01 +0530 Subject: [PATCH 05/13] Resolve conflicts --- .../api/nativeimpl/ExternResponseProcessor.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java index ff1d8184b..75a02f842 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java @@ -169,8 +169,8 @@ private ExternResponseProcessor() { } public static Object processResponse(Environment env, BObject response, BTypedesc targetType, - boolean requireValidation) { - return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env); + boolean requireValidation, boolean requireLaxDataBinding) { + return getResponseWithType(response, targetType.getDescribingType(), requireValidation, requireLaxDataBinding, env); } public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDesc, BObject status, BMap headers, @@ -191,7 +191,7 @@ public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDes } private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, - Environment env) { + boolean requireLaxDataBinding, Environment env) { long responseStatusCode = getStatusCode(response); // Find the most specific status code record type @@ -205,7 +205,7 @@ private static Object getResponseWithType(BObject response, Type targetType, boo if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { try { - return generateStatusCodeResponseType(response, requireValidation, env, statusCodeRecordType, + return generateStatusCodeResponseType(response, requireValidation, requireLaxDataBinding, env, statusCodeRecordType, responseStatusCode); } catch (StatusCodeBindingException exp) { return getStatusCodeResponseDataBindingError(env, response, exp.getMessage(), exp.getBError(), @@ -221,8 +221,8 @@ private static long getStatusCode(BObject response) { return response.getIntValue(StringUtils.fromString(STATUS_CODE)); } - private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Environment env, - RecordType statusCodeRecordType, long responseStatusCode) + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, boolean requireLaxDataBinding, + Environment env, RecordType statusCodeRecordType, long responseStatusCode) throws StatusCodeBindingException { String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { @@ -255,7 +255,7 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r return env.getRuntime().callMethod(response, BUILD_STATUS_CODE_RESPONSE, null, Objects.isNull(payloadType) ? null : ValueCreator.createTypedescValue(payloadType), ValueCreator.createTypedescValue(statusCodeRecordType), - requireValidation, status, headers, mediaType, + requireValidation, requireLaxDataBinding, status, headers, mediaType, isDefaultStatusCodeResponseType(statusCodeRecordType)); } catch (BError error) { return createHttpError(STATUS_CODE_RES_CREATION_FAILED, STATUS_CODE_RESPONSE_BINDING_ERROR, error); From 8beb685047b006fe8d9cbba1fada68f9259796aa Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 08:51:09 +0530 Subject: [PATCH 06/13] Resolve checkstyle errors --- .../http/api/nativeimpl/ExternResponseProcessor.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java index 75a02f842..e8840f4f2 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java @@ -170,7 +170,8 @@ private ExternResponseProcessor() { public static Object processResponse(Environment env, BObject response, BTypedesc targetType, boolean requireValidation, boolean requireLaxDataBinding) { - return getResponseWithType(response, targetType.getDescribingType(), requireValidation, requireLaxDataBinding, env); + return getResponseWithType(response, targetType.getDescribingType(), requireValidation, + requireLaxDataBinding, env); } public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDesc, BObject status, BMap headers, @@ -205,8 +206,8 @@ private static Object getResponseWithType(BObject response, Type targetType, boo if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { try { - return generateStatusCodeResponseType(response, requireValidation, requireLaxDataBinding, env, statusCodeRecordType, - responseStatusCode); + return generateStatusCodeResponseType(response, requireValidation, requireLaxDataBinding, env, + statusCodeRecordType, responseStatusCode); } catch (StatusCodeBindingException exp) { return getStatusCodeResponseDataBindingError(env, response, exp.getMessage(), exp.getBError(), isDefaultStatusCodeResponseType(statusCodeRecordType), exp.getErrorType()); @@ -221,8 +222,9 @@ private static long getStatusCode(BObject response) { return response.getIntValue(StringUtils.fromString(STATUS_CODE)); } - private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, boolean requireLaxDataBinding, - Environment env, RecordType statusCodeRecordType, long responseStatusCode) + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, + boolean requireLaxDataBinding, Environment env, + RecordType statusCodeRecordType, long responseStatusCode) throws StatusCodeBindingException { String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { From e2e7d59ff931fbf9de864e1c86a9c84d7b4a262b Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 09:18:56 +0530 Subject: [PATCH 07/13] Fix the spotbugs --- .../main/java/io/ballerina/stdlib/http/api/HttpResource.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java index 47563b9db..cf9de9d80 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java @@ -336,10 +336,6 @@ private void setLaxDataBinding(boolean laxDataBinding) { this.laxDataBinding = laxDataBinding; } - private boolean getLaxDataBinding() { - return this.laxDataBinding; - } - private void updateLinkedResources(Object[] links) { for (Object link : links) { BMap linkMap = (BMap) link; From 320a58622fc426b8765fbde3dbf856d9050523cb Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 11:26:48 +0530 Subject: [PATCH 08/13] Add laxDataBinding to payload builder --- .../stdlib/http/api/HttpConstants.java | 3 +++ .../stdlib/http/api/HttpResource.java | 7 ++++++- .../stdlib/http/api/InterceptorResource.java | 2 +- .../api/service/signature/ParamHandler.java | 12 +++++++---- .../api/service/signature/PayloadParam.java | 11 ++++++---- .../builder/AbstractPayloadBuilder.java | 14 ++++++------- .../signature/builder/ArrayBuilder.java | 6 ++++-- .../signature/builder/JsonPayloadBuilder.java | 6 ++++-- .../converter/JsonToRecordConverter.java | 20 +++++++++++++------ 9 files changed, 54 insertions(+), 27 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java index 130e87af2..95925c2c0 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java @@ -502,6 +502,9 @@ public final class HttpConstants { public static final String ALLOW_DATA_PROJECTION = "allowDataProjection"; public static final String PARSER_AS_TYPE_OPTIONS = "Options"; + public static final String NIL_AS_OPTIONAL = "nilAsOptional"; + public static final String ABSENT_AS_NILABLE = "absentAsNilable"; + //Client Endpoint (CallerActions) public static final String CLIENT_ENDPOINT_SERVICE_URI = "url"; public static final String CLIENT_ENDPOINT_CONFIG = "config"; diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java index cf9de9d80..8687f3ec4 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java @@ -336,6 +336,10 @@ private void setLaxDataBinding(boolean laxDataBinding) { this.laxDataBinding = laxDataBinding; } + private boolean getLaxDataBinding() { + return this.laxDataBinding; + } + private void updateLinkedResources(Object[] links) { for (Object link : links) { BMap linkMap = (BMap) link; @@ -392,7 +396,8 @@ private static void processResourceCors(HttpResource resource, HttpService servi } private void prepareAndValidateSignatureParams() { - paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, this.getConstraintValidation()); + paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, this.getConstraintValidation(), + this.getLaxDataBinding()); } @Override diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java index d3bf2c504..9f7acb558 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java @@ -153,7 +153,7 @@ public static InterceptorResource buildInterceptorResource(MethodType resource, } private void prepareAndValidateSignatureParams() { - paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, false); + paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, false, false); } @Override 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 d884f7b91..1da2a5109 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 @@ -74,6 +74,7 @@ public class ParamHandler { private final AllQueryParams queryParams = new AllQueryParams(); private final AllHeaderParams headerParams = new AllHeaderParams(); private final boolean constraintValidation; + private final boolean laxDataBinding; private static final String PARAM_ANNOT_PREFIX = "$param$."; private static final MapType MAP_TYPE = TypeCreator.createMapType( @@ -92,11 +93,13 @@ public class ParamHandler { + ANN_NAME_CACHE; public static final String QUERY_ANNOTATION = ModuleUtils.getHttpPackageIdentifier() + COLON + ANN_NAME_QUERY; - public ParamHandler(ResourceMethodType resource, int pathParamCount, boolean constraintValidation) { + public ParamHandler(ResourceMethodType resource, int pathParamCount, boolean constraintValidation, + boolean laxDataBinding) { this.resource = resource; this.pathParamCount = pathParamCount; this.paramTypes = getParameterTypes(resource); this.constraintValidation = constraintValidation; + this.laxDataBinding = laxDataBinding; populatePathParamTokens(resource, pathParamCount); populatePayloadAndHeaderParamTokens(resource); validateSignatureParams(); @@ -202,7 +205,7 @@ private void populatePayloadAndHeaderParamTokens(ResourceMethodType balResource) String key = ((BString) objKey).getValue(); if (PAYLOAD_ANNOTATION.equals(key)) { if (payloadParam == null) { - createPayloadParam(paramName, annotations, constraintValidation); + createPayloadParam(paramName, annotations, constraintValidation, laxDataBinding); } else { throw HttpUtil.createHttpError( "invalid multiple '" + PROTOCOL_HTTP + COLON + ANN_NAME_PAYLOAD + "' annotation usage"); @@ -237,8 +240,9 @@ private boolean isAllowedResourceParamAnnotation(String key) { return PAYLOAD_ANNOTATION.equals(key) || CALLER_ANNOTATION.equals(key) || HEADER_ANNOTATION.equals(key); } - private void createPayloadParam(String paramName, BMap annotations, boolean constraintValidation) { - this.payloadParam = new PayloadParam(paramName, constraintValidation); + private void createPayloadParam(String paramName, BMap annotations, boolean constraintValidation, + boolean laxDataBinding) { + this.payloadParam = new PayloadParam(paramName, constraintValidation, laxDataBinding); BMap mapValue = annotations.getMapValue(StringUtils.fromString(PAYLOAD_ANNOTATION)); Object mediaType = mapValue.get(HttpConstants.ANN_FIELD_MEDIA_TYPE); if (mediaType instanceof BString) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java index 375f289a1..67d2bdc03 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java @@ -59,10 +59,12 @@ public class PayloadParam implements Parameter { private final List mediaTypes = new ArrayList<>(); private Type customParameterType; private final boolean requireConstraintValidation; + private final boolean laxDataBinding; - PayloadParam(String token, boolean constraintValidation) { + PayloadParam(String token, boolean constraintValidation, boolean laxDataBinding) { this.token = token; this.requireConstraintValidation = constraintValidation; + this.laxDataBinding = laxDataBinding; } public void init(Type type, Type customParameterType, int index) { @@ -150,7 +152,8 @@ private void populateFeedWithAlreadyBuiltPayload(Object[] paramFeed, BObject inR if (actualTypeTag == TypeTags.BYTE_TAG) { paramFeed[index] = validateConstraints(dataSource); } else if (actualTypeTag == TypeTags.RECORD_TYPE_TAG) { - dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly); + dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly, + laxDataBinding); paramFeed[index] = validateConstraints(dataSource); } else { throw HttpUtil.createHttpError("incompatible element type found inside an array " + @@ -158,7 +161,7 @@ private void populateFeedWithAlreadyBuiltPayload(Object[] paramFeed, BObject inR } break; case TypeTags.RECORD_TYPE_TAG: - dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly); + dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly, laxDataBinding); paramFeed[index] = validateConstraints(dataSource); break; default: @@ -174,7 +177,7 @@ private int populateFeedWithFreshPayload(HttpCarbonMessage inboundMessage, Objec BObject inRequestEntity, int index, Type payloadType) { try { String contentType = HttpUtil.getContentTypeFromTransportMessage(inboundMessage); - AbstractPayloadBuilder payloadBuilder = getBuilder(contentType, payloadType); + AbstractPayloadBuilder payloadBuilder = getBuilder(contentType, payloadType, laxDataBinding); Object payloadBuilderValue = payloadBuilder.getValue(inRequestEntity, this.readonly); paramFeed[index] = validateConstraints(payloadBuilderValue); inboundMessage.setProperty(HttpConstants.ENTITY_OBJ, inRequestEntity); diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java index 222542c3a..aaf97f84a 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java @@ -63,9 +63,9 @@ public abstract class AbstractPayloadBuilder { */ public abstract Object getValue(BObject inRequestEntity, boolean readonly); - public static AbstractPayloadBuilder getBuilder(String contentType, Type payloadType) { + public static AbstractPayloadBuilder getBuilder(String contentType, Type payloadType, boolean laxDataBinding) { if (contentType == null || contentType.isEmpty()) { - return getBuilderFromType(payloadType); + return getBuilderFromType(payloadType, laxDataBinding); } contentType = contentType.toLowerCase(Locale.getDefault()).trim(); String baseType = HeaderUtil.getHeaderValue(contentType); @@ -78,22 +78,22 @@ public static AbstractPayloadBuilder getBuilder(String contentType, Type payload } else if (baseType.matches(OCTET_STREAM_PATTERN)) { return new BinaryPayloadBuilder(payloadType); } else if (baseType.matches(JSON_PATTERN)) { - return new JsonPayloadBuilder(payloadType); + return new JsonPayloadBuilder(payloadType, laxDataBinding); } else { - return getBuilderFromType(payloadType); + return getBuilderFromType(payloadType, laxDataBinding); } } - private static AbstractPayloadBuilder getBuilderFromType(Type payloadType) { + private static AbstractPayloadBuilder getBuilderFromType(Type payloadType, boolean laxDataBinding) { switch (payloadType.getTag()) { case STRING_TAG: return new StringPayloadBuilder(payloadType); case XML_TAG: return new XmlPayloadBuilder(payloadType); case ARRAY_TAG: - return new ArrayBuilder(payloadType); + return new ArrayBuilder(payloadType, laxDataBinding); default: - return new JsonPayloadBuilder(payloadType); + return new JsonPayloadBuilder(payloadType, laxDataBinding); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java index 0c9e0927d..329c6b28a 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java @@ -30,9 +30,11 @@ */ public class ArrayBuilder extends AbstractPayloadBuilder { private final Type payloadType; + private final boolean laxDataBinding; - public ArrayBuilder(Type payloadType) { + public ArrayBuilder(Type payloadType, boolean laxDataBinding) { this.payloadType = payloadType; + this.laxDataBinding = laxDataBinding; } @Override @@ -41,6 +43,6 @@ public Object getValue(BObject entity, boolean readonly) { if (elementType.getTag() == TypeTags.BYTE_TAG) { return new BinaryPayloadBuilder(payloadType).getValue(entity, readonly); } - return new JsonPayloadBuilder(payloadType).getValue(entity, readonly); + return new JsonPayloadBuilder(payloadType, laxDataBinding).getValue(entity, readonly); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java index be220443e..37d49918b 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java @@ -33,9 +33,11 @@ */ public class JsonPayloadBuilder extends AbstractPayloadBuilder { private final Type payloadType; + private final boolean laxDataBinding; - public JsonPayloadBuilder(Type payloadType) { + public JsonPayloadBuilder(Type payloadType, boolean laxDataBinding) { this.payloadType = payloadType; + this.laxDataBinding = laxDataBinding; } @Override @@ -43,7 +45,7 @@ public Object getValue(BObject entity, boolean readonly) { // Following can be removed based on the solution of // https://github.com/ballerina-platform/ballerina-lang/issues/35780 if (isSubtypeOfAllowedType(payloadType, TypeTags.RECORD_TYPE_TAG)) { - return JsonToRecordConverter.convert(payloadType, entity, readonly); + return JsonToRecordConverter.convert(payloadType, entity, readonly, laxDataBinding); } Object bJson = EntityBodyHandler.constructJsonDataSource(entity); EntityBodyHandler.addJsonMessageDataSource(entity, bJson); diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java index 84971fddd..fa5ef3e39 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java @@ -36,6 +36,9 @@ import static io.ballerina.stdlib.http.api.HttpConstants.ALLOW_DATA_PROJECTION; import static io.ballerina.stdlib.http.api.HttpConstants.ENABLE_CONSTRAINT_VALIDATION; import static io.ballerina.stdlib.http.api.HttpConstants.PARSER_AS_TYPE_OPTIONS; +import static io.ballerina.stdlib.http.api.HttpConstants.NIL_AS_OPTIONAL; +import static io.ballerina.stdlib.http.api.HttpConstants.ABSENT_AS_NILABLE; + /** * The converter binds the JSON payload to a record. @@ -44,18 +47,18 @@ */ public class JsonToRecordConverter { - public static Object convert(Type type, BObject entity, boolean readonly) { - Object recordEntity = getRecordEntity(entity, type); + public static Object convert(Type type, BObject entity, boolean readonly, boolean laxDataBinding) { + Object recordEntity = getRecordEntity(entity, type, laxDataBinding); if (readonly && recordEntity instanceof BRefValue) { ((BRefValue) recordEntity).freezeDirect(); } return recordEntity; } - private static Object getRecordEntity(BObject entity, Type entityBodyType) { + private static Object getRecordEntity(BObject entity, Type entityBodyType, boolean laxDataBinding) { Object bjson = EntityBodyHandler.getMessageDataSource(entity) == null ? getBJsonValue(entity) : EntityBodyHandler.getMessageDataSource(entity); - Object result = getRecord(entityBodyType, bjson); + Object result = getRecord(entityBodyType, bjson, laxDataBinding); if (result instanceof BError) { throw (BError) result; } @@ -69,12 +72,17 @@ private static Object getRecordEntity(BObject entity, Type entityBodyType) { * @param bJson Represents the json value that needs to be converted * @return the relevant ballerina record or object */ - private static Object getRecord(Type entityBodyType, Object bJson) { + private static Object getRecord(Type entityBodyType, Object bJson, boolean laxDataBinding) { try { Map valueMap = new HashMap<>(); Boolean bool = Boolean.FALSE; valueMap.put(ENABLE_CONSTRAINT_VALIDATION, bool); - valueMap.put(ALLOW_DATA_PROJECTION, bool); + if (laxDataBinding) { + valueMap.put(NIL_AS_OPTIONAL, Boolean.TRUE); + valueMap.put(ABSENT_AS_NILABLE, Boolean.TRUE); + } else { + valueMap.put(ALLOW_DATA_PROJECTION, bool); + } BMap mapValue = ValueCreator.createRecordValue( io.ballerina.lib.data.ModuleUtils.getModule(), PARSER_AS_TYPE_OPTIONS, valueMap); From 8a2a8045f06e9411d46b4803041f2597c2168cb0 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 12:17:03 +0530 Subject: [PATCH 09/13] Add default value to laxDataBinding in HttpService --- .../src/main/java/io/ballerina/stdlib/http/api/HttpService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index ec2a17639..f58e67c19 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -98,7 +98,7 @@ public class HttpService implements Service { private BArray balInterceptorServicesArray; private byte[] introspectionPayload = new byte[0]; private Boolean constraintValidation = true; - private Boolean laxDataBinding; + private Boolean laxDataBinding = false; protected HttpService(BObject service, String basePath) { this.balService = service; From e0f92dd7fc5555e6a676b2b5bce8bdef222f15aa Mon Sep 17 00:00:00 2001 From: Sachin Akash Date: Tue, 26 Nov 2024 13:15:38 +0530 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: Krishnananthalingam Tharmigan <63336800+TharmiganK@users.noreply.github.com> --- ballerina/http_types.bal | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ballerina/http_types.bal b/ballerina/http_types.bal index e82358098..9ba1929a0 100644 --- a/ballerina/http_types.bal +++ b/ballerina/http_types.bal @@ -105,7 +105,10 @@ public type CommonClientConfiguration record {| boolean validation = true; # Provides settings related to client socket configuration ClientSocketConfig socketConfig = {}; - # Enables or disalbles relaxed data binding on the client side. Disabled by default + # Enables or disables relaxed data binding on the client side. Disabled by default. + # When enabled, the JSON data will be projected to the Ballerina record type and + # during the projection, nil values will be considered as optional fields and + # absent fields will be considered for nilable types boolean laxDataBinding = false; |}; From 0e9ea3864f3a650166d97c77e6abca6bd8ab86a1 Mon Sep 17 00:00:00 2001 From: Sachin Akash Date: Tue, 26 Nov 2024 13:44:43 +0530 Subject: [PATCH 11/13] Fix typos Co-authored-by: Sumudu Nissanka --- ballerina/http_status_code_client.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/http_status_code_client.bal b/ballerina/http_status_code_client.bal index a701ee28b..38668bc32 100644 --- a/ballerina/http_status_code_client.bal +++ b/ballerina/http_status_code_client.bal @@ -26,7 +26,7 @@ import ballerina/observe; # HTTP service in resilient manner # + cookieStore - Stores the cookies of the client # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package -# + requireLaxDataBinding - Enables or disalbles relaxed data binding on the client side. +# + requireLaxDataBinding - Enables or disables relaxed data binding on the client side. public client isolated class StatusCodeClient { *StatusCodeClientObject; From edcf8416f7fcbf579e2fb6c39da953eae8fa151e Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 13:48:22 +0530 Subject: [PATCH 12/13] Add descriptive doc comments --- ballerina/http_annotation.bal | 4 +++- ballerina/http_client_endpoint.bal | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index eab45740f..05d7501e7 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -27,7 +27,9 @@ # + validation - Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default # + serviceType - The service object type which defines the service contract. This is auto-generated at compile-time # + basePath - Base path to be used with the service implementation. This is only allowed on service contract types -# + laxDataBinding - Enables or disalbles relaxed data binding on the service side. Disabled by default +# + laxDataBinding - Enables or disables relaxed data binding on the service side. Disabled by default. +# When enabled, the JSON data will be projected to the Ballerina record type and during the projection, +# nil values will be considered as optional fields and absent fields will be considered for nilable types public type HttpServiceConfig record {| string host = "b7a.default"; CompressionConfig compression = {}; diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 93551ee4c..c27d4989c 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -28,7 +28,7 @@ import ballerina/time; # HTTP service in resilient manner # + cookieStore - Stores the cookies of the client # + requireValidation - Enables the inbound payload validation functionalty which provided by the constraint package -# + requireLaxDataBinding - Enables or disalbles relaxed data binding. +# + requireLaxDataBinding - Enables or disables relaxed data binding. public client isolated class Client { *ClientObject; From 3d3a1c167c32b264cac0a3771f5c8cc2ddc19a30 Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 13:53:48 +0530 Subject: [PATCH 13/13] Fix reviewed changes --- ballerina/http_client_payload_builder.bal | 24 ++++++++----------- .../converter/JsonToRecordConverter.java | 1 - 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ballerina/http_client_payload_builder.bal b/ballerina/http_client_payload_builder.bal index f5ca177b8..ed6a76606 100644 --- a/ballerina/http_client_payload_builder.bal +++ b/ballerina/http_client_payload_builder.bal @@ -162,27 +162,23 @@ isolated function jsonPayloadBuilder(Response response, TargetType targetType, b isolated function nonNilablejsonPayloadBuilder(Response response, typedesc targetType, boolean requireLaxDataBinding) returns anydata|ClientError { json payload = check response.getJsonPayload(); - var result = jsondata:parseAsType( - payload, - requireLaxDataBinding - ? {allowDataProjection: {nilAsOptionalField: true, absentAsNilableType: true}, enableConstraintValidation: false} - : {enableConstraintValidation: false, allowDataProjection: false}, - targetType - ); + jsondata:Options jsonParserOptions = { + enableConstraintValidation: false, + allowDataProjection: requireLaxDataBinding ? {nilAsOptionalField: true, absentAsNilableType: true} : false + }; + var result = jsondata:parseAsType(payload, jsonParserOptions, targetType); return result is error ? createPayloadBindingError(result) : result; } isolated function nilablejsonPayloadBuilder(Response response, typedesc targetType, boolean requireLaxDataBinding) returns anydata|ClientError { json|ClientError payload = response.getJsonPayload(); + jsondata:Options jsonParserOptions = { + enableConstraintValidation: false, + allowDataProjection: requireLaxDataBinding ? {nilAsOptionalField: true, absentAsNilableType: true} : false + }; if payload is json { - var result = jsondata:parseAsType( - payload, - requireLaxDataBinding - ? {allowDataProjection: {nilAsOptionalField: true, absentAsNilableType: true}, enableConstraintValidation: false} - : {enableConstraintValidation: false, allowDataProjection: false}, - targetType - ); + var result = jsondata:parseAsType(payload, jsonParserOptions, targetType); return result is error ? createPayloadBindingError(result) : result; } else { return payload is NoContentError ? () : payload; diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java index fa5ef3e39..bf2e91bc2 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java @@ -39,7 +39,6 @@ import static io.ballerina.stdlib.http.api.HttpConstants.NIL_AS_OPTIONAL; import static io.ballerina.stdlib.http.api.HttpConstants.ABSENT_AS_NILABLE; - /** * The converter binds the JSON payload to a record. *