diff --git a/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal b/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal index f33da5019a..5cff4ba297 100644 --- a/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal +++ b/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal @@ -240,53 +240,17 @@ service /api on new http:Listener(statusCodeBindingPort2) { } } -final http:Client albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`); +final http:StatusCodeClient albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`); @test:Config {} function testGetSuccessStatusCodeResponse() returns error? { - Album album = check albumClient->/albums/'1; - Album expectedAlbum = albums.get("1"); - test:assertEquals(album, expectedAlbum, "Invalid album returned"); - AlbumFound albumFound = check albumClient->get("/albums/1"); + Album expectedAlbum = albums.get("1"); test:assertEquals(albumFound.body, expectedAlbum, "Invalid album returned"); test:assertEquals(albumFound.headers.user\-id, "user-1", "Invalid user-id header"); test:assertEquals(albumFound.headers.req\-id, 1, "Invalid req-id header"); test:assertEquals(albumFound.mediaType, "application/json", "Invalid media type"); - http:Response res = check albumClient->/albums/'1; - test:assertEquals(res.statusCode, 200, "Invalid status code"); - json payload = check res.getJsonPayload(); - album = check payload.fromJsonWithType(); - test:assertEquals(album, expectedAlbum, "Invalid album returned"); - - Album|AlbumFound res1 = check albumClient->get("/albums/1"); - if res1 is AlbumFound { - test:assertEquals(res1.body, expectedAlbum, "Invalid album returned"); - test:assertEquals(res1.headers.user\-id, "user-1", "Invalid user-id header"); - test:assertEquals(res1.headers.req\-id, 1, "Invalid req-id header"); - test:assertEquals(res1.mediaType, "application/json", "Invalid media type"); - } else { - test:assertFail("Invalid response type"); - } - - AlbumFound|Album res2 = check albumClient->/albums/'1; - if res2 is AlbumFound { - test:assertEquals(res2.body, expectedAlbum, "Invalid album returned"); - test:assertEquals(res2.headers.user\-id, "user-1", "Invalid user-id header"); - test:assertEquals(res2.headers.req\-id, 1, "Invalid req-id header"); - test:assertEquals(res2.mediaType, "application/json", "Invalid media type"); - } else { - test:assertFail("Invalid response type"); - } - - Album|AlbumNotFound res3 = check albumClient->get("/albums/1"); - if res3 is Album { - test:assertEquals(res3, expectedAlbum, "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - AlbumFound|AlbumNotFound res4 = check albumClient->/albums/'1; if res4 is AlbumFound { test:assertEquals(res4.body, expectedAlbum, "Invalid album returned"); @@ -297,37 +261,10 @@ function testGetSuccessStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - Album|AlbumFound|AlbumNotFound res5 = check albumClient->get("/albums/1"); - if res5 is AlbumFound { - test:assertEquals(res5.body, expectedAlbum, "Invalid album returned"); - test:assertEquals(res5.headers.user\-id, "user-1", "Invalid user-id header"); - test:assertEquals(res5.headers.req\-id, 1, "Invalid req-id header"); - test:assertEquals(res5.mediaType, "application/json", "Invalid media type"); - } else { - test:assertFail("Invalid response type"); - } - - Album|AlbumNotFound|http:Response res6 = check albumClient->/albums/'1; - if res6 is Album { - test:assertEquals(res6, expectedAlbum, "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - AlbumNotFound|http:Response res7 = check albumClient->get("/albums/1"); - if res7 is http:Response { - test:assertEquals(res.statusCode, 200, "Invalid status code"); - payload = check res.getJsonPayload(); - album = check payload.fromJsonWithType(); - test:assertEquals(album, expectedAlbum, "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - AlbumNotFound|error res8 = albumClient->/albums/'1; if res8 is error { - test:assertTrue(res8 is http:PayloadBindingError); - test:assertEquals(res8.message(), "incompatible http_client_tests:AlbumNotFound found for response with 200", + test:assertTrue(res8 is http:StatusCodeResponseBindingError); + test:assertEquals(res8.message(), "incompatible AlbumNotFound found for response with 200", "Invalid error message"); error? cause = res8.cause(); if cause is error { @@ -347,80 +284,20 @@ function testGetFailureStatusCodeResponse() returns error? { test:assertEquals(albumNotFound.headers.req\-id, 1, "Invalid req-id header"); test:assertEquals(albumNotFound.mediaType, "application/json", "Invalid media type"); - http:Response res = check albumClient->get("/albums/4"); - test:assertEquals(res.statusCode, 404, "Invalid status code"); - json payload = check res.getJsonPayload(); - ErrorMessage errorMessage = check payload.fromJsonWithType(); - test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message"); - - Album|AlbumNotFound res1 = check albumClient->/albums/'4; - if res1 is AlbumNotFound { - test:assertEquals(res1.body, expectedErrorMessage, "Invalid error message"); - test:assertEquals(res1.headers.user\-id, "user-1", "Invalid user-id header"); - test:assertEquals(res1.headers.req\-id, 1, "Invalid req-id header"); - test:assertEquals(res1.mediaType, "application/json", "Invalid media type"); - } else { - test:assertFail("Invalid response type"); - } - - AlbumNotFound|http:Response res2 = check albumClient->get("/albums/4"); - if res2 is AlbumNotFound { - test:assertEquals(res2.body, expectedErrorMessage, "Invalid error message"); - test:assertEquals(res2.headers.user\-id, "user-1", "Invalid user-id header"); - test:assertEquals(res2.headers.req\-id, 1, "Invalid req-id header"); - test:assertEquals(res2.mediaType, "application/json", "Invalid media type"); - } else { - test:assertFail("Invalid response type"); - } - - Album|http:Response res3 = check albumClient->/albums/'4; - if res3 is http:Response { - test:assertEquals(res3.statusCode, 404, "Invalid status code"); - payload = check res3.getJsonPayload(); - errorMessage = check payload.fromJsonWithType(); - test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message"); - } else { - test:assertFail("Invalid response type"); - } - - http:Response|AlbumFound res4 = check albumClient->get("/albums/4"); - if res4 is http:Response { - test:assertEquals(res4.statusCode, 404, "Invalid status code"); - payload = check res4.getJsonPayload(); - errorMessage = check payload.fromJsonWithType(); - test:assertEquals(errorMessage, expectedErrorMessage, "Invalid error message"); - } else { - test:assertFail("Invalid response type"); - } - - Album|error res5 = albumClient->/albums/'4; - if res5 is error { - test:assertTrue(res5 is http:ClientRequestError); - test:assertEquals(res5.message(), "Not Found", "Invalid error message"); - test:assertEquals(res5.detail()["statusCode"], 404, "Invalid status code"); - test:assertEquals(res5.detail()["body"], expectedErrorMessage, "Invalid error message"); - if res5.detail()["headers"] is map { - map headers = check res5.detail()["headers"].ensureType(); - test:assertEquals(headers.get("user-id")[0], "user-1", "Invalid user-id header"); - test:assertEquals(headers.get("req-id")[0], "1", "Invalid req-id header"); - } - - } else { - test:assertFail("Invalid response type"); - } - AlbumFound|error res6 = albumClient->get("/albums/4"); if res6 is error { test:assertTrue(res6 is http:ClientRequestError); - test:assertEquals(res6.message(), "Not Found", "Invalid error message"); + test:assertTrue(res6 is http:StatusCodeResponseBindingError); + test:assertEquals(res6.message(), "incompatible AlbumFound found for response with 404", "Invalid error message"); test:assertEquals(res6.detail()["statusCode"], 404, "Invalid status code"); test:assertEquals(res6.detail()["body"], expectedErrorMessage, "Invalid error message"); if res6.detail()["headers"] is map { map headers = check res6.detail()["headers"].ensureType(); test:assertEquals(headers.get("user-id")[0], "user-1", "Invalid user-id header"); test:assertEquals(headers.get("req-id")[0], "1", "Invalid req-id header"); + } else { + test:assertFail("Invalid headers type"); } - } else { test:assertFail("Invalid response type"); } @@ -428,48 +305,6 @@ function testGetFailureStatusCodeResponse() returns error? { @test:Config {} function testUnionPayloadBindingWithStatusCodeResponse() returns error? { - Album|AlbumNotFound|map|json res1 = check albumClient->/albums/'1; - if res1 is Album { - test:assertEquals(res1, albums.get("1"), "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - map|AlbumNotFound|Album|json res2 = check albumClient->get("/albums/1"); - if res2 is map { - test:assertEquals(res2, albums.get("1"), "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - Album|MockAlbum|AlbumNotFound res3 = check albumClient->/albums/'1; - if res3 is Album { - test:assertEquals(res3, albums.get("1"), "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - MockAlbum|Album|AlbumNotFound res4 = check albumClient->get("/albums/1"); - if res4 is MockAlbum { - test:assertEquals(res4, {...albums.get("1"), "type": "mock"}, "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - AlbumUnion1|AlbumNotFound res5 = check albumClient->/albums/'1; - if res5 is Album { - test:assertEquals(res5, albums.get("1"), "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - - AlbumUnion2|AlbumNotFound res6 = check albumClient->get("/albums/1"); - if res6 is MockAlbum { - test:assertEquals(res6, {...albums.get("1"), "type": "mock"}, "Invalid album returned"); - } else { - test:assertFail("Invalid response type"); - } - AlbumFound|AlbumNotFound|AlbumFoundMock1 res7 = check albumClient->/albums/'1; if res7 is AlbumFound { test:assertEquals(res7.body, albums.get("1"), "Invalid album returned"); @@ -611,7 +446,7 @@ function testStatusCodeBindingWithConstraintsSuccess() returns error? { test:assertEquals(res1.headers.req\-id, 1, "Invalid req-id header"); test:assertEquals(res1.mediaType, "application/json", "Invalid media type"); - AlbumFoundWithConstraints|AlbumFound|Album|http:Response res2 = check albumClient->get("/albums/2"); + AlbumFoundWithConstraints|AlbumFound res2 = check albumClient->get("/albums/2"); if res2 is AlbumFoundWithConstraints { test:assertEquals(res2.body, albums.get("2"), "Invalid album returned"); test:assertEquals(res2.headers.user\-id, "user-1", "Invalid user-id header"); @@ -652,7 +487,7 @@ function testStatusCodeBindingWithConstraintsFailure() returns error? { test:assertFail("Invalid response type"); } - AlbumFoundWithInvalidConstraints3|Album|error res3 = albumClient->/albums/'2; + AlbumFoundWithInvalidConstraints3|error res3 = albumClient->/albums/'2; if res3 is error { test:assertTrue(res3 is http:MediaTypeValidationError); test:assertEquals(res3.message(), "media-type binding failed: Validation failed for " + diff --git a/ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal b/ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal index f5e25b4a6b..d393775dcf 100644 --- a/ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal +++ b/ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal @@ -17,7 +17,7 @@ import ballerina/http; import ballerina/test; -final http:Client statusCodeBindingClient1 = check new (string `localhost:${statusCodeBindingPort1}`); +final http:StatusCodeClient statusCodeBindingClient1 = check new (string `localhost:${statusCodeBindingPort1}`); service /api on new http:Listener(statusCodeBindingPort1) { diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 8edb594675..55d0f0eb5c 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -88,7 +88,7 @@ public client isolated class Client { } external; private isolated function processPost(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->post(path, req); @@ -130,7 +130,7 @@ public client isolated class Client { } external; private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->put(path, req); @@ -172,7 +172,7 @@ public client isolated class Client { } external; private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->patch(path, req); @@ -214,7 +214,7 @@ public client isolated class Client { } external; private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->delete(path, req); @@ -277,7 +277,7 @@ public client isolated class Client { } external; private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); Response|ClientError response = self.httpClient->get(path, message = req); if observabilityEnabled && response is Response { @@ -313,7 +313,7 @@ public client isolated class Client { } external; private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); Response|ClientError response = self.httpClient->options(path, message = req); if observabilityEnabled && response is Response { @@ -340,7 +340,7 @@ public client isolated class Client { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); Response|ClientError response = self.httpClient->execute(httpVerb, path, req); @@ -363,7 +363,7 @@ public client isolated class Client { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Response|ClientError response = self.httpClient->forward(path, request); if observabilityEnabled && response is Response { addObservabilityInformation(path, request.method, response.statusCode, self.url); @@ -677,12 +677,57 @@ isolated function createResponseError(int statusCode, string reasonPhrase, map headers, anydata body = ()) returns ClientError { + if generalError { + return error StatusCodeResponseBindingError(reasonPhrase, statusCode = statusCode, headers = headers, body = body); + } + if 100 <= statusCode && statusCode <= 399 { + return error StatusCodeBindingSuccessError(reasonPhrase, statusCode = statusCode, headers = headers, body = body); + } else if 400 <= statusCode && statusCode <= 499 { + return error StatusCodeBindingClientRequestError(reasonPhrase, statusCode = statusCode, headers = headers, body = body); + } else { + return error StatusCodeBindingRemoteServerError(reasonPhrase, statusCode = statusCode, headers = headers, body = body); + } +} + isolated function processResponse(Response|ClientError response, TargetType targetType, boolean requireValidation) - returns Response|anydata|StatusCodeResponse|ClientError { + returns Response|anydata|ClientError { + if targetType is typedesc || response is ClientError { + return response; + } + int statusCode = response.statusCode; + if 400 <= statusCode && statusCode <= 599 { + string reasonPhrase = response.reasonPhrase; + map headers = getHeaders(response); + anydata|error payload = getPayload(response); + if payload is error { + if payload is NoContentError { + return createResponseError(statusCode, reasonPhrase, headers); + } + return error PayloadBindingClientError("http:ApplicationResponseError creation failed: " + statusCode.toString() + + " response payload extraction failed", payload); + } else { + return createResponseError(statusCode, reasonPhrase, headers, payload); + } + } + if targetType is typedesc { + anydata payload = check performDataBinding(response, targetType); + if requireValidation { + return performDataValidation(payload, targetType); + } + return payload; + } else { + panic error GenericClientError("invalid payload target type"); + } +} + +isolated function processResponseNew(Response|ClientError response, typedesc targetType, boolean requireValidation) + returns StatusCodeResponse|ClientError { if response is ClientError { return response; } - return externProcessResponse(response, targetType, requireValidation); + return externProcessResponseNew(response, targetType, requireValidation); } isolated function externProcessResponse(Response response, TargetType targetType, boolean requireValidation) @@ -690,3 +735,9 @@ isolated function externProcessResponse(Response response, TargetType targetType 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternResponseProcessor", name: "processResponse" } external; + +isolated function externProcessResponseNew(Response response, typedesc targetType, boolean requireValidation) + returns StatusCodeResponse|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternResponseProcessor", + name: "processResponse" +} external; diff --git a/ballerina/http_errors.bal b/ballerina/http_errors.bal index 7d15fd6d85..435508e692 100644 --- a/ballerina/http_errors.bal +++ b/ballerina/http_errors.bal @@ -308,4 +308,10 @@ public type ResourceDispatchingServerError httpscerr:InternalServerErrorError & type InternalResourceDispatchingServerError ResourceDispatchingServerError & InternalError; # Represents the client status code binding error -public type StatusCodeRecordBindingError distinct ClientError; +public type StatusCodeResponseBindingError distinct ClientError & error; + +type StatusCodeBindingClientRequestError distinct StatusCodeResponseBindingError & ClientRequestError; + +type StatusCodeBindingRemoteServerError distinct StatusCodeResponseBindingError & RemoteServerError; + +type StatusCodeBindingSuccessError distinct StatusCodeResponseBindingError; diff --git a/ballerina/http_response.bal b/ballerina/http_response.bal index 9a8361c40c..751f56a5b9 100644 --- a/ballerina/http_response.bal +++ b/ballerina/http_response.bal @@ -540,6 +540,20 @@ public class Response { return cookiesInResponse; } + isolated function buildStatusCodeResponse(typedesc? payloadType, typedesc statusCodeResType, + boolean requireValidation, Status status, map headers, string? mediaType) + returns StatusCodeResponse|ClientError { + if payloadType !is () { + anydata|ClientError payload = self.performDataBinding(payloadType, requireValidation); + if payload is ClientError { + return payload; + } + return externBuildStatusCodeResponse(statusCodeResType, status, headers, payload, mediaType); + } else { + return externBuildStatusCodeResponse(statusCodeResType, status, headers, (), ()); + } + } + isolated function performDataBinding(typedesc targetType, boolean requireValidation) returns anydata|ClientError { anydata payload = check performDataBinding(self, targetType); if requireValidation { @@ -548,19 +562,18 @@ public class Response { return payload; } - isolated function getApplicationResponseError() returns ClientError { - string reasonPhrase = self.reasonPhrase; + isolated function getStatusCodeResponseBindingError(string reasonPhrase, boolean generalError) returns ClientError { map headers = getHeaders(self); anydata|error payload = getPayload(self); int statusCode = self.statusCode; if payload is error { if payload is NoContentError { - return createResponseError(statusCode, reasonPhrase, headers); + return createStatusCodeResponseBindingError(generalError, statusCode, reasonPhrase, headers); } - return error PayloadBindingClientError("http:ApplicationResponseError creation failed: " + statusCode.toString() + + return error PayloadBindingClientError("http:StatusCodeBindingError creation failed: " + statusCode.toString() + " response payload extraction failed", payload); } else { - return createResponseError(statusCode, reasonPhrase, headers, payload); + return createStatusCodeResponseBindingError(generalError, statusCode, reasonPhrase, headers, payload); } } } @@ -647,3 +660,10 @@ isolated function externResponseHasHeader(Response response, string headerName, 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternHeaders", name: "hasHeader" } external; + +isolated function externBuildStatusCodeResponse(typedesc statusCodeResType, Status status, + map headers, anydata body, string? mediaType) + returns StatusCodeResponse|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.nativeimpl.ExternResponseProcessor", + name: "buildStatusCodeResponse" +} external; diff --git a/ballerina/http_status_code_client.bal b/ballerina/http_status_code_client.bal new file mode 100644 index 0000000000..09574f7c95 --- /dev/null +++ b/ballerina/http_status_code_client.bal @@ -0,0 +1,479 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/jballerina.java; +import ballerina/observe; + +# The HTTP status code client provides the capability for initiating contact with a remote HTTP service. The API it +# provides includes the functions for the standard HTTP methods forwarding a received request and sending requests +# using custom HTTP verbs. The responses can be binded to `http:StatusCodeResponse` types + +# + url - Target service url +# + httpClient - Chain of different HTTP clients which provides the capability for initiating contact with a remote +# 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 +public client isolated class StatusCodeClient { + *StatusCodeClientObject; + + private final string url; + private CookieStore? cookieStore = (); + final HttpClient httpClient; + private final boolean requireValidation; + + # 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, + # security, circuit breaking). + # + # + url - URL of the target service + # + config - The configurations to be used when initializing the `client` + # + return - The `client` or an `http:ClientError` if the initialization failed + public isolated function init(string url, *ClientConfiguration config) returns ClientError? { + self.url = url; + var cookieConfigVal = config.cookieConfig; + if cookieConfigVal is CookieConfig { + if cookieConfigVal.enabled { + self.cookieStore = new(cookieConfigVal?.persistentCookieHandler); + } + } + self.httpClient = check initialize(url, config, self.cookieStore); + self.requireValidation = config.validation; + return; + } + + # The client resource function to send HTTP POST requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function post [PathParamType ...path](RequestMessage message, map? headers = (), string? + mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "postResource" + } external; + + # The `Client.post()` function can be used to send HTTP POST requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function post(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processPost(string path, RequestMessage message, typedesc targetType, + string? mediaType, map? headers) returns StatusCodeResponse|ClientError { + Request req = check buildRequest(message, mediaType); + populateOptions(req, mediaType, headers); + Response|ClientError response = self.httpClient->post(path, req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_POST, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The client resource function to send HTTP PUT requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function put [PathParamType ...path](RequestMessage message, map? headers = (), string? + mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "putResource" + } external; + + # The `Client.put()` function can be used to send HTTP PUT requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + mediaType - The MIME type header of the request entity + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function put(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processPut(string path, RequestMessage message, typedesc targetType, + string? mediaType, map? headers) returns StatusCodeResponse|ClientError { + Request req = check buildRequest(message, mediaType); + populateOptions(req, mediaType, headers); + Response|ClientError response = self.httpClient->put(path, req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_PUT, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The client resource function to send HTTP PATCH requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function patch [PathParamType ...path](RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "patchResource" + } external; + + # The `Client.patch()` function can be used to send HTTP PATCH requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + mediaType - The MIME type header of the request entity + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function patch(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processPatch(string path, RequestMessage message, typedesc targetType, + string? mediaType, map? headers) returns StatusCodeResponse|ClientError { + Request req = check buildRequest(message, mediaType); + populateOptions(req, mediaType, headers); + Response|ClientError response = self.httpClient->patch(path, req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_PATCH, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The client resource function to send HTTP DELETE requests to HTTP endpoints. + # + # + path - Request path + # + message - An optional HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function delete [PathParamType ...path](RequestMessage message = (), map? headers = (), + string? mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "deleteResource" + } external; + + # The `Client.delete()` function can be used to send HTTP DELETE requests to HTTP endpoints. + # + # + path - Resource path + # + message - An optional HTTP outbound request message or any allowed payload + # + mediaType - The MIME type header of the request entity + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function delete(string path, RequestMessage message = (), + map? headers = (), string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processDelete(string path, RequestMessage message, typedesc targetType, + string? mediaType, map? headers) returns StatusCodeResponse|ClientError { + Request req = check buildRequest(message, mediaType); + populateOptions(req, mediaType, headers); + Response|ClientError response = self.httpClient->delete(path, req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_DELETE, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The client resource function to send HTTP HEAD requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + params - The query parameters + # + return - The response or an `http:ClientError` if failed to establish the communication with the upstream server + isolated resource function head [PathParamType ...path](map? headers = (), *QueryParams params) + returns Response|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "headResource" + } external; + + # The `Client.head()` function can be used to send HTTP HEAD requests to HTTP endpoints. + # + # + path - Resource path + # + headers - The entity headers + # + return - The response or an `http:ClientError` if failed to establish the communication with the upstream server + remote isolated function head(string path, map? headers = ()) returns Response|ClientError { + Request req = buildRequestWithHeaders(headers); + Response|ClientError response = self.httpClient->head(path, message = req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_HEAD, response.statusCode, self.url); + } + return response; + } + + # The client resource function to send HTTP GET requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function get [PathParamType ...path](map? headers = (), typedesc targetType = <>, + *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "getResource" + } external; + + # The `Client.get()` function can be used to send HTTP GET requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function get(string path, map? headers = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processGet(string path, map? headers, typedesc targetType) + returns StatusCodeResponse|ClientError { + Request req = buildRequestWithHeaders(headers); + Response|ClientError response = self.httpClient->get(path, message = req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_GET, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function options [PathParamType ...path](map? headers = (), typedesc targetType = <>, + *QueryParams params) returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction", + name: "optionsResource" + } external; + + # The `Client.options()` function can be used to send HTTP OPTIONS requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function options(string path, map? headers = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processOptions(string path, map? headers, typedesc targetType) + returns StatusCodeResponse|ClientError { + Request req = buildRequestWithHeaders(headers); + Response|ClientError response = self.httpClient->options(path, message = req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, HTTP_OPTIONS, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # Invokes an HTTP call with the specified HTTP verb. + # + # + httpVerb - HTTP verb value + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + mediaType - The MIME type header of the request entity + # + headers - The entity headers + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function execute(string httpVerb, string path, RequestMessage message, + map? headers = (), string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processExecute(string httpVerb, string path, RequestMessage message, + typedesc targetType, string? mediaType, map? headers) + returns StatusCodeResponse|ClientError { + Request req = check buildRequest(message, mediaType); + populateOptions(req, mediaType, headers); + Response|ClientError response = self.httpClient->execute(httpVerb, path, req); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, httpVerb, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb + # + # + path - Request path + # + request - An HTTP inbound request message + # + targetType - HTTP status code response, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function forward(string path, Request request, typedesc targetType = <>) + returns targetType|ClientError = @java:Method { + 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" + } external; + + private isolated function processForward(string path, Request request, typedesc targetType) + returns StatusCodeResponse|ClientError { + Response|ClientError response = self.httpClient->forward(path, request); + if observabilityEnabled && response is Response { + addObservabilityInformation(path, request.method, response.statusCode, self.url); + } + return processResponseNew(response, targetType, self.requireValidation); + } + + # Submits an HTTP request to a service with the specified HTTP verb. + # The `Client->submit()` function does not give out a `http:Response` as the result. + # Rather it returns an `http:HttpFuture` which can be used to do further interactions with the endpoint. + # + # + httpVerb - The HTTP verb value + # + path - The resource path + # + message - An HTTP outbound request or any allowed payload + # + return - An `http:HttpFuture` that represents an asynchronous service invocation or else an `http:ClientError` if the submission fails + remote isolated function submit(string httpVerb, string path, RequestMessage message) returns HttpFuture|ClientError { + Request req = check buildRequest(message, ()); + return self.httpClient->submit(httpVerb, path, req); + } + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` related to a previous asynchronous invocation + # + return - An `http:Response` message or else an `http: ClientError` if the invocation fails + remote isolated function getResponse(HttpFuture httpFuture) returns Response|ClientError { + Response|ClientError response = self.httpClient->getResponse(httpFuture); + if observabilityEnabled && response is Response { + string statusCode = response.statusCode.toString(); + _ = checkpanic observe:addTagToSpan(HTTP_STATUS_CODE, statusCode); + _ = checkpanic observe:addTagToMetrics(HTTP_STATUS_CODE_GROUP, getStatusCodeRange(statusCode)); + } + return response; + } + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` relates to a previous asynchronous invocation + # + return - A `boolean`, which represents whether an `http:PushPromise` exists + remote isolated function hasPromise(HttpFuture httpFuture) returns boolean { + return self.httpClient->hasPromise(httpFuture); + } + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` related to a previous asynchronous invocation + # + return - An `http:PushPromise` message or else an `http:ClientError` if the invocation fails + remote isolated function getNextPromise(HttpFuture httpFuture) returns PushPromise|ClientError { + return self.httpClient->getNextPromise(httpFuture); + } + + # Passes the request to an actual network call. + # + # + promise - The related `http:PushPromise` + # + return - A promised `http:Response` message or else an `http:ClientError` if the invocation fails + remote isolated function getPromisedResponse(PushPromise promise) returns Response|ClientError { + Response|ClientError response = self.httpClient->getPromisedResponse(promise); + if observabilityEnabled && response is Response { + addObservabilityInformation(promise.path, promise.method, response.statusCode, self.url); + } + return response; + } + + # This just pass the request to actual network call. + # + # + promise - The Push Promise to be rejected + remote isolated function rejectPromise(PushPromise promise) { + return self.httpClient->rejectPromise(promise); + } + + # Retrieves the cookie store of the client. + # + # + return - The cookie store related to the client + public isolated function getCookieStore() returns CookieStore? { + lock { + return self.cookieStore; + } + } + + # The circuit breaker client related method to force the circuit into a closed state in which it will allow + # requests regardless of the error percentage until the failure threshold exceeds. + public isolated function circuitBreakerForceClose() { + do { + CircuitBreakerClient cbClient = check trap self.httpClient; + cbClient.forceClose(); + } on fail error err { + panic error ClientError("illegal method invocation. 'circuitBreakerForceClose()' is allowed for clients " + + "which have configured with circuit breaker configurations", err); + } + } + + # The circuit breaker client related method to force the circuit into a open state in which it will suspend all + # requests until `resetTime` interval exceeds. + public isolated function circuitBreakerForceOpen() { + do { + CircuitBreakerClient cbClient = check trap self.httpClient; + cbClient.forceOpen(); + } on fail error err { + panic error ClientError("illegal method invocation. 'circuitBreakerForceOpen()' is allowed for clients " + + "which have configured with circuit breaker configurations", err); + } + } + + # The circuit breaker client related method to provides the `http:CircuitState` of the circuit breaker. + # + # + return - The current `http:CircuitState` of the circuit breaker + public isolated function getCircuitBreakerCurrentState() returns CircuitState { + do { + CircuitBreakerClient cbClient = check trap self.httpClient; + return cbClient.getCurrentState(); + } on fail error err { + panic error ClientError("illegal method invocation. 'getCircuitBreakerCurrentState()' is allowed for " + + "clients which have configured with circuit breaker configurations", err); + } + } +} diff --git a/ballerina/http_status_code_client_object.bal b/ballerina/http_status_code_client_object.bal new file mode 100644 index 0000000000..72ceaa92e6 --- /dev/null +++ b/ballerina/http_status_code_client_object.bal @@ -0,0 +1,246 @@ +// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# The representation of the http Status Code Client object type for managing resilient clients. +public type StatusCodeClientObject client object { + + # The client resource function to send HTTP POST requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function post [PathParamType ...path](RequestMessage message, map? headers = (), string? + mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError; + + # The client resource function to send HTTP PUT requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function put [PathParamType ...path](RequestMessage message, map? headers = (), string? + mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError; + + # The client resource function to send HTTP PATCH requests to HTTP endpoints. + # + # + path - Request path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function patch [PathParamType ...path](RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError; + + # The client resource function to send HTTP DELETE requests to HTTP endpoints. + # + # + path - Request path + # + message - An optional HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function delete [PathParamType ...path](RequestMessage message = (), map? headers = (), + string? mediaType = (), typedesc targetType = <>, *QueryParams params) returns targetType|ClientError; + + # The client resource function to send HTTP HEAD requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + params - The query parameters + # + return - The response or an `http:ClientError` if failed to establish the communication with the upstream server + isolated resource function head [PathParamType ...path](map? headers = (), *QueryParams params) + returns Response|ClientError; + + # The client resource function to send HTTP GET requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function get [PathParamType ...path](map? headers = (), typedesc targetType = <>, + *QueryParams params) returns targetType|ClientError; + + # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + params - The query parameters + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + isolated resource function options [PathParamType ...path](map? headers = (), typedesc targetType = <>, + *QueryParams params) returns targetType|ClientError; + + # The `Client.post()` function can be used to send HTTP POST requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function post(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.put()` function can be used to send HTTP PUT requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function put(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.patch()` function can be used to send HTTP PATCH requests to HTTP endpoints. + # + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function patch(string path, RequestMessage message, map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.delete()` function can be used to send HTTP DELETE requests to HTTP endpoints. + # + # + path - Resource path + # + message - An optional HTTP outbound request message or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function delete(string path, RequestMessage message = (), map? headers = (), + string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.head()` function can be used to send HTTP HEAD requests to HTTP endpoints. + # + # + path - Resource path + # + headers - The entity headers + # + return - The response or an `http:ClientError` if failed to establish the communication with the upstream server + remote isolated function head(string path, map? headers = ()) returns Response|ClientError; + + # The `Client.get()` function can be used to send HTTP GET requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function get( string path, map? headers = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.options()` function can be used to send HTTP OPTIONS requests to HTTP endpoints. + # + # + path - Request path + # + headers - The entity headers + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function options( string path, map? headers = (), typedesc targetType = <>) + returns targetType|ClientError; + + # Invokes an HTTP call with the specified HTTP verb. + # + # + httpVerb - HTTP verb value + # + path - Resource path + # + message - An HTTP outbound request or any allowed payload + # + headers - The entity headers + # + mediaType - The MIME type header of the request entity + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function execute(string httpVerb, string path, RequestMessage message, + map? headers = (), string? mediaType = (), typedesc targetType = <>) + returns targetType|ClientError; + + # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb + # + # + path - Request path + # + request - An HTTP inbound request message + # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding + # + return - The response or the payload (if the `targetType` is configured) or an `http:ClientError` if failed to + # establish the communication with the upstream server or a data binding failure + remote isolated function forward(string path, Request request, typedesc targetType = <>) + returns targetType|ClientError; + + # Submits an HTTP request to a service with the specified HTTP verb. + # The `Client->submit()` function does not give out a `http:Response` as the result. + # Rather it returns an `http:HttpFuture` which can be used to do further interactions with the endpoint. + # + # + httpVerb - The HTTP verb value + # + path - The resource path + # + message - An HTTP outbound request message or any payload of type `string`, `xml`, `json`, `byte[]` + # or `mime:Entity[]` + # + return - An `http:HttpFuture` that represents an asynchronous service invocation or else an `http:ClientError` if the submission fails + remote isolated function submit(string httpVerb, string path, RequestMessage message) + returns HttpFuture|ClientError; + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` related to a previous asynchronous invocation + # + return - An `http:Response` message or else an `http: ClientError` if the invocation fails + remote isolated function getResponse(HttpFuture httpFuture) returns Response|ClientError; + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` relates to a previous asynchronous invocation + # + return - A `boolean`, which represents whether an `http:PushPromise` exists + remote isolated function hasPromise(HttpFuture httpFuture) returns boolean; + + # This just pass the request to actual network call. + # + # + httpFuture - The `http:HttpFuture` related to a previous asynchronous invocation + # + return - An `http:PushPromise` message or else an `http:ClientError` if the invocation fails + remote isolated function getNextPromise(HttpFuture httpFuture) returns PushPromise|ClientError; + + # Passes the request to an actual network call. + # + # + promise - The related `http:PushPromise` + # + return - A promised `http:Response` message or else an `http:ClientError` if the invocation fails + remote isolated function getPromisedResponse(PushPromise promise) returns Response|ClientError; + + # This just pass the request to actual network call. + # + # + promise - The Push Promise to be rejected + remote isolated function rejectPromise(PushPromise promise); +}; diff --git a/ballerina/http_types.bal b/ballerina/http_types.bal index 27f53c5a41..f4d95bab04 100644 --- a/ballerina/http_types.bal +++ b/ballerina/http_types.bal @@ -29,7 +29,7 @@ public type Service distinct service object { }; # The types of data values that are expected by the HTTP `client` to return after the data binding operation. -public type TargetType typedesc; +public type TargetType typedesc; # Defines the HTTP operations related to circuit breaker, failover and load balancer. # diff --git a/ballerina/resiliency_failover_client.bal b/ballerina/resiliency_failover_client.bal index c6692cf468..fdab8636cd 100644 --- a/ballerina/resiliency_failover_client.bal +++ b/ballerina/resiliency_failover_client.bal @@ -99,7 +99,7 @@ public client isolated class FailoverClient { } external; private isolated function processPost(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_POST); @@ -144,7 +144,7 @@ public client isolated class FailoverClient { } external; private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PUT); @@ -189,7 +189,7 @@ public client isolated class FailoverClient { } external; private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PATCH); @@ -234,7 +234,7 @@ public client isolated class FailoverClient { } external; private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_DELETE); @@ -303,7 +303,7 @@ public client isolated class FailoverClient { } external; private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performFailoverAction(path, req, HTTP_GET); if result is HttpFuture { @@ -342,7 +342,7 @@ public client isolated class FailoverClient { } external; private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performFailoverAction(path, req, HTTP_OPTIONS); if result is HttpFuture { @@ -372,7 +372,7 @@ public client isolated class FailoverClient { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performExecuteAction(path, req, httpVerb); @@ -398,7 +398,7 @@ public client isolated class FailoverClient { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { var result = self.performFailoverAction(path, request, HTTP_FORWARD); if result is HttpFuture { return getInvalidTypeError(); diff --git a/ballerina/resiliency_load_balance_client.bal b/ballerina/resiliency_load_balance_client.bal index 071eabde05..1ced9bbb27 100644 --- a/ballerina/resiliency_load_balance_client.bal +++ b/ballerina/resiliency_load_balance_client.bal @@ -92,7 +92,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processPost(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_POST); @@ -131,7 +131,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PUT); @@ -170,7 +170,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PATCH); @@ -209,7 +209,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { + string? mediaType, map? headers) returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_DELETE); @@ -265,7 +265,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_GET); return processResponse(result, targetType, self.requireValidation); @@ -298,7 +298,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_OPTIONS); return processResponse(result, targetType, self.requireValidation); @@ -322,7 +322,7 @@ public client isolated class LoadBalanceClient { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceExecuteAction(path, req, httpVerb); @@ -342,7 +342,7 @@ public client isolated class LoadBalanceClient { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|StatusCodeResponse|anydata|ClientError { + returns Response|anydata|ClientError { var result = self.performLoadBalanceAction(path, request, HTTP_FORWARD); return processResponse(result, targetType, self.requireValidation); } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpErrorType.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpErrorType.java index e04405e5dd..658fd61222 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpErrorType.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpErrorType.java @@ -84,7 +84,7 @@ public enum HttpErrorType { INTERNAL_RESOURCE_PATH_VALIDATION_ERROR("InternalResourcePathValidationError"), INTERNAL_HEADER_VALIDATION_LISTENER_ERROR("InternalHeaderValidationListenerError"), INTERNAL_QUERY_PARAM_VALIDATION_ERROR("InternalQueryParameterValidationError"), - STATUS_CODE_RECORD_BINDING_ERROR("StatusCodeRecordBindingError"), + STATUS_CODE_RESPONSE_BINDING_ERROR("StatusCodeResponseBindingError"), HEADER_NOT_FOUND_CLIENT_ERROR("HeaderNotFoundClientError"), HEADER_BINDING_CLIENT_ERROR("HeaderBindingClientError"), HEADER_VALIDATION_CLIENT_ERROR("HeaderValidationClientError"), 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 84b58154e2..0b72c05fa6 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 @@ -18,11 +18,10 @@ package io.ballerina.stdlib.http.api.nativeimpl; import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Future; import io.ballerina.runtime.api.PredefinedTypes; -import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.async.Callback; -import io.ballerina.runtime.api.creators.ErrorCreator; import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; @@ -50,14 +49,12 @@ import io.ballerina.stdlib.http.api.ValueCreatorUtils; import io.netty.handler.codec.http.HttpHeaders; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CountDownLatch; import static io.ballerina.stdlib.http.api.HttpConstants.HTTP_HEADERS; import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE; @@ -73,7 +70,6 @@ import static io.ballerina.stdlib.http.api.HttpErrorType.MEDIA_TYPE_BINDING_CLIENT_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.MEDIA_TYPE_VALIDATION_CLIENT_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.PAYLOAD_BINDING_CLIENT_ERROR; -import static io.ballerina.stdlib.http.api.HttpErrorType.STATUS_CODE_RECORD_BINDING_ERROR; import static io.ballerina.stdlib.http.api.HttpUtil.createHttpError; /** @@ -89,13 +85,12 @@ public final class ExternResponseProcessor { private static final String UNSUPPORTED_HEADERS_TYPE = "unsupported headers type: %s"; private static final String UNSUPPORTED_STATUS_CODE = "unsupported status code: %d"; private static final String INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE = "incompatible %s found for response with %d"; - private static final String NO_ANYDATA_TYPE_FOUND_IN_THE_TARGET_TYPE = "no 'anydata' type found in the target type"; - private static final String PAYLOAD_BINDING_FAILED = "payload binding failed"; private static final String MEDIA_TYPE_BINDING_FAILED = "media-type binding failed"; private static final String APPLICATION_RES_ERROR_CREATION_FAILED = "http:ApplicationResponseError creation failed"; + private static final String STATUS_CODE_RES_CREATION_FAILED = "http:StatusCodeResponse creation failed"; - private static final String PERFORM_DATA_BINDING = "performDataBinding"; - private static final String GET_APPLICATION_RESPONSE_ERROR = "getApplicationResponseError"; + private static final String GET_STATUS_CODE_RESPONSE_BINDING_ERROR = "getStatusCodeResponseBindingError"; + private static final String BUILD_STATUS_CODE_RESPONSE = "buildStatusCodeResponse"; private static final Map STATUS_CODE_OBJS = new HashMap<>(); @@ -169,87 +164,100 @@ private ExternResponseProcessor() { public static Object processResponse(Environment env, BObject response, BTypedesc targetType, boolean requireValidation) { - return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env.getRuntime()); + return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env); + } + + public static Object buildStatusCodeResponse(BTypedesc statusCodeResponseTypeDesc, BObject status, BMap headers, + Object body, Object mediaType) { + if (statusCodeResponseTypeDesc.getDescribingType() instanceof RecordType statusCodeRecordType) { + BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_STATUS_FIELD), status); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headers); + if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD)) { + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); + } + if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_BODY_FIELD), body); + } + return statusCodeRecord; + } + return createHttpError(STATUS_CODE_RES_CREATION_FAILED, CLIENT_ERROR); } private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, - Runtime runtime) { + Environment env) { long responseStatusCode = getStatusCode(response); Optional statusCodeResponseType = getStatusCodeResponseType(targetType, Long.toString(responseStatusCode)); if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { - return generateStatusCodeResponseType(response, requireValidation, runtime, statusCodeRecordType, + return generateStatusCodeResponseType(response, requireValidation, env, statusCodeRecordType, responseStatusCode); - } else if ((399 < responseStatusCode) && (responseStatusCode < 600)) { - return hasHttpResponseType(targetType) ? response : getApplicationResponseError(runtime, response); - } else { - return generatePayload(response, targetType, requireValidation, runtime); - } - } - - private static Object generatePayload(BObject response, Type targetType, boolean requireValidation, - Runtime runtime) { - try { - return getPayload(runtime, response, getAnydataType(targetType), requireValidation); - } catch (BError e) { - if (hasHttpResponseType(targetType)) { - return response; - } - return createHttpError(String.format(INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE, targetType, - getStatusCode(response)), PAYLOAD_BINDING_CLIENT_ERROR, e); } + String reasonPhrase = String.format(INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE, targetType.getName(), + responseStatusCode); + return getStatusCodeResponseBindingError(env, response, reasonPhrase, false); } private static long getStatusCode(BObject response) { return response.getIntValue(StringUtils.fromString(STATUS_CODE)); } - private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Runtime runtime, + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Environment env, RecordType statusCodeRecordType, long responseStatusCode) { - BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); - String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { - return createHttpError(String.format(UNSUPPORTED_STATUS_CODE, responseStatusCode), - STATUS_CODE_RECORD_BINDING_ERROR); + return getStatusCodeResponseBindingError(env, response, String.format(UNSUPPORTED_STATUS_CODE, + responseStatusCode), true); } - populateStatusCodeObject(statusCodeObjName, statusCodeRecord); + Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); - Object headerMap = getHeaders(response, requireValidation, statusCodeRecordType); - if (headerMap instanceof BError) { - return headerMap; + Object headers = getHeaders(env, response, requireValidation, statusCodeRecordType); + if (headers instanceof BError) { + return headers; } - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); + Object mediaType = null; if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD)) { - Object mediaType = getMediaType(response, requireValidation, statusCodeRecordType); + mediaType = getMediaType(response, requireValidation, statusCodeRecordType); if (mediaType instanceof BError) { return mediaType; } - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); } + Type payloadType = null; if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { - Object payload = getBody(response, requireValidation, runtime, statusCodeRecordType); - if (payload instanceof BError) { - return payload; - } - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_BODY_FIELD), payload); + payloadType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_BODY_FIELD).getFieldType(); } - return statusCodeRecord; + Object[] paramFeed = getParamFeedForStatusCodeBinding(requireValidation, statusCodeRecordType, payloadType, + status, headers, mediaType); + return getStatusCodeResponse(env, response, paramFeed); } - private static Object getBody(BObject response, boolean requireValidation, Runtime runtime, - RecordType statusCodeRecordType) { - Type bodyType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_BODY_FIELD).getFieldType(); - return getPayload(runtime, response, bodyType, requireValidation); - } - - private static Object getHeaders(BObject response, boolean requireValidation, RecordType statusCodeRecordType) { + private static Object[] getParamFeedForStatusCodeBinding(boolean requireValidation, RecordType statusCodeType, + Type payloadType, Object status, Object headers, + Object mediaType) { + Object[] paramFeed = new Object[12]; + 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; + return paramFeed; + } + + private static Object getHeaders(Environment env, BObject response, boolean requireValidation, + RecordType statusCodeRecordType) { Type headersType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_HEADERS_FIELD).getFieldType(); - return getHeadersMap(response, headersType, requireValidation); + return getHeadersMap(env, response, headersType, requireValidation); } private static Object getMediaType(BObject response, boolean requireValidation, RecordType statusCodeRecordType) { @@ -257,42 +265,6 @@ private static Object getMediaType(BObject response, boolean requireValidation, return getMediaType(response, mediaTypeType, requireValidation); } - private static void populateStatusCodeObject(String statusCodeObjName, BMap statusCodeRecord) { - Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_STATUS_FIELD), status); - } - - private static Type getAnydataType(Type targetType) { - List anydataTypes = extractAnydataTypes(targetType, new ArrayList<>()); - if (anydataTypes.isEmpty()) { - throw ErrorCreator.createError(StringUtils.fromString(NO_ANYDATA_TYPE_FOUND_IN_THE_TARGET_TYPE)); - } else if (anydataTypes.size() == 1) { - return anydataTypes.get(0); - } else { - return TypeCreator.createUnionType(anydataTypes); - } - } - - private static List extractAnydataTypes(Type targetType, List anydataTypes) { - if (targetType.isAnydata()) { - anydataTypes.add(targetType); - return anydataTypes; - } - - switch (targetType.getTag()) { - case TypeTags.UNION_TAG: - List memberTypes = ((UnionType) targetType).getMemberTypes(); - for (Type memberType : memberTypes) { - extractAnydataTypes(memberType, anydataTypes); - } - return anydataTypes; - case TypeTags.TYPE_REFERENCED_TYPE_TAG: - return extractAnydataTypes(TypeUtils.getImpliedType(targetType), anydataTypes); - default: - return anydataTypes; - } - } - private static Object getMediaType(BObject response, Type mediaTypeType, boolean requireValidation) { String contentType = getContentType(response); try { @@ -310,7 +282,8 @@ private static String getContentType(BObject response) { return httpHeaders.get("Content-Type"); } - private static Object getHeadersMap(BObject response, Type headersType, boolean requireValidation) { + private static Object getHeadersMap(Environment env, BObject response, Type headersType, + boolean requireValidation) { HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData(HTTP_HEADERS); Type headersImpliedType = TypeUtils.getImpliedType(headersType); if (headersImpliedType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { @@ -324,8 +297,8 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean } else if (headersImpliedType.getTag() == TypeTags.RECORD_TYPE_TAG) { headerMap = createHeaderRecord(httpHeaders, (RecordType) headersImpliedType); } else { - return createHttpError(String.format(UNSUPPORTED_HEADERS_TYPE, headersType), - STATUS_CODE_RECORD_BINDING_ERROR); + return getStatusCodeResponseBindingError(env, response, String.format(UNSUPPORTED_HEADERS_TYPE, + headersImpliedType.getName()), true); } if (headerMap instanceof BError) { @@ -341,16 +314,6 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean } } - private static boolean hasHttpResponseType(Type targetType) { - return switch (targetType.getTag()) { - case TypeTags.OBJECT_TYPE_TAG -> true; - case TypeTags.UNION_TAG -> ((UnionType) targetType).getMemberTypes().stream().anyMatch( - ExternResponseProcessor::hasHttpResponseType); - case TypeTags.TYPE_REFERENCED_TYPE_TAG -> hasHttpResponseType(TypeUtils.getImpliedType(targetType)); - default -> false; - }; - } - private static Optional getStatusCodeResponseType(Type targetType, String statusCode) { if (isStatusCodeResponseType(targetType)) { if (getStatusCode(targetType).equals(statusCode)) { @@ -492,10 +455,6 @@ private static Object convertHeaderValues(List headerValues, Type header } } - private static Object getPayload(Runtime runtime, BObject response, Type payloadType, boolean requireValidation) { - return performBalDataBinding(runtime, response, payloadType, requireValidation); - } - private static boolean isOptionalHeaderField(Field headerField) { return SymbolFlags.isFlagOn(headerField.getFlags(), SymbolFlags.OPTIONAL); } @@ -516,65 +475,49 @@ private static Object validateConstraints(boolean requireValidation, Object conv return convertedValue; } - public static Object performBalDataBinding(Runtime runtime, BObject response, Type payloadType, - boolean requireValidation) { - final Object[] payload = new Object[1]; - CountDownLatch countDownLatch = new CountDownLatch(1); + private static Object getStatusCodeResponseBindingError(Environment env, BObject response, String reasonPhrase, + boolean generalError) { + Future balFuture = env.markAsync(); Callback returnCallback = new Callback() { @Override public void notifySuccess(Object result) { - payload[0] = result; - countDownLatch.countDown(); + balFuture.complete(result); } @Override - public void notifyFailure(BError result) { - payload[0] = result; - countDownLatch.countDown(); + public void notifyFailure(BError bError) { + BError error = createHttpError(APPLICATION_RES_ERROR_CREATION_FAILED, PAYLOAD_BINDING_CLIENT_ERROR, + bError); + balFuture.complete(error); } }; Object[] paramFeed = new Object[4]; - paramFeed[0] = ValueCreator.createTypedescValue(payloadType); + paramFeed[0] = StringUtils.fromString(reasonPhrase); paramFeed[1] = true; - paramFeed[2] = requireValidation; + paramFeed[2] = generalError; paramFeed[3] = true; - runtime.invokeMethodAsyncSequentially(response, PERFORM_DATA_BINDING, null, - ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ANY, - paramFeed); - try { - countDownLatch.await(); - return payload[0]; - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return createHttpError(PAYLOAD_BINDING_FAILED, CLIENT_ERROR, HttpUtil.createError(exception)); - } + env.getRuntime().invokeMethodAsyncSequentially(response, GET_STATUS_CODE_RESPONSE_BINDING_ERROR, null, + ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ERROR, paramFeed); + return null; } - public static Object getApplicationResponseError(Runtime runtime, BObject response) { - final Object[] clientError = new Object[1]; - CountDownLatch countDownLatch = new CountDownLatch(1); + private static Object getStatusCodeResponse(Environment env, BObject response, Object[] paramFeed) { + Future balFuture = env.markAsync(); Callback returnCallback = new Callback() { @Override public void notifySuccess(Object result) { - clientError[0] = result; - countDownLatch.countDown(); + balFuture.complete(result); } @Override - public void notifyFailure(BError result) { - clientError[0] = result; - countDownLatch.countDown(); + public void notifyFailure(BError bError) { + BError error = createHttpError(STATUS_CODE_RES_CREATION_FAILED, CLIENT_ERROR, bError); + balFuture.complete(error); } }; - runtime.invokeMethodAsyncSequentially(response, GET_APPLICATION_RESPONSE_ERROR, null, - ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ERROR); - try { - countDownLatch.await(); - return clientError[0]; - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - return createHttpError(APPLICATION_RES_ERROR_CREATION_FAILED, - PAYLOAD_BINDING_CLIENT_ERROR, HttpUtil.createError(exception)); - } + env.getRuntime().invokeMethodAsyncSequentially(response, BUILD_STATUS_CODE_RESPONSE, null, + ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ANY, + paramFeed); + return null; } }