From f1a79b3d7899fd499b27056cda789cb0e15e7454 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Sun, 24 Mar 2024 07:31:20 +0530 Subject: [PATCH 01/30] Update the minor version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a63cd02f9a..ea13e70e60 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.10.13-SNAPSHOT +version=2.11.0-SNAPSHOT ballerinaLangVersion=2201.8.0 ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 From 7eda0cd803f552337b95b3d4958cf546ec70e207 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Sun, 24 Mar 2024 07:31:39 +0530 Subject: [PATCH 02/30] Add initial support for status code response binding --- ballerina/http_client_endpoint.bal | 63 ++- ballerina/http_response.bal | 24 + ballerina/http_types.bal | 2 +- .../stdlib/http/api/HttpConstants.java | 2 - .../stdlib/http/api/HttpErrorType.java | 1 + .../stdlib/http/api/ValueCreatorUtils.java | 4 + .../nativeimpl/ExternResponseProcessor.java | 466 ++++++++++++++++++ 7 files changed, 543 insertions(+), 19 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 9e0ca8e123..3da0fe3e03 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -88,14 +88,17 @@ public client isolated class Client { } external; private isolated function processPost(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP PUT requests to HTTP endpoints. @@ -130,14 +133,17 @@ public client isolated class Client { } external; private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP PATCH requests to HTTP endpoints. @@ -172,14 +178,17 @@ public client isolated class Client { } external; private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP DELETE requests to HTTP endpoints. @@ -214,14 +223,17 @@ public client isolated class Client { } external; private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP HEAD requests to HTTP endpoints. @@ -277,13 +289,16 @@ public client isolated class Client { } external; private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. @@ -313,13 +328,16 @@ public client isolated class Client { } external; private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # Invokes an HTTP call with the specified HTTP verb. @@ -340,14 +358,17 @@ public client isolated class Client { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|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 processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb @@ -363,12 +384,15 @@ public client isolated class Client { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Response|ClientError response = self.httpClient->forward(path, request); if observabilityEnabled && response is Response { addObservabilityInformation(path, request.method, response.statusCode, self.url); } - return processResponse(response, targetType, self.requireValidation); + if response is ClientError { + return response; + } + return externProcessResponse(response, targetType, self.requireValidation); } # Submits an HTTP request to a service with the specified HTTP verb. @@ -707,3 +731,10 @@ isolated function createResponseError(int statusCode, string reasonPhrase, map targetType, boolean requireValidation) returns anydata|ClientError { + anydata payload = check performDataBinding(self, targetType); + if requireValidation { + return performDataValidation(payload, targetType); + } + return payload; + } + + isolated function getApplicationResponseError() returns ClientError { + string reasonPhrase = self.reasonPhrase; + 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 error PayloadBindingClientError("http:ApplicationResponseError creation failed: " + statusCode.toString() + + " response payload extraction failed", payload); + } else { + return createResponseError(statusCode, reasonPhrase, headers, payload); + } + } } isolated function externCreateNewResEntity(Response response) returns mime:Entity = diff --git a/ballerina/http_types.bal b/ballerina/http_types.bal index f4d95bab04..27f53c5a41 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/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java index b31d83f132..a2bb57fbb2 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 @@ -258,8 +258,6 @@ public final class HttpConstants { public static final String HTTP_ERROR_RECORD = "HTTPError"; public static final BString HTTP_ERROR_MESSAGE = StringUtils.fromString("message"); public static final BString HTTP_ERROR_STATUS_CODE = StringUtils.fromString("statusCode"); - public static final String HTTP_CLIENT_REQUEST_ERROR = "ClientRequestError"; - public static final String HTTP_REMOTE_SERVER_ERROR = "RemoteServerError"; // ServeConnector struct indices public static final BString HTTP_CONNECTOR_CONFIG_FIELD = StringUtils.fromString("config"); 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 3353093ab0..8b91e75fa8 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 @@ -63,6 +63,7 @@ public enum HttpErrorType { INVALID_CONTENT_LENGTH("InvalidContentLengthError"), HEADER_NOT_FOUND_ERROR("HeaderNotFoundError"), CLIENT_ERROR("ClientError"), + PAYLOAD_BINDING_CLIENT_ERROR("PayloadBindingClientError"), INTERNAL_PAYLOAD_BINDING_LISTENER_ERROR("InternalPayloadBindingListenerError"), INTERNAL_PAYLOAD_VALIDATION_LISTENER_ERROR("InternalPayloadValidationListenerError"), INTERNAL_HEADER_BINDING_ERROR("InternalHeaderBindingError"), diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/ValueCreatorUtils.java b/native/src/main/java/io/ballerina/stdlib/http/api/ValueCreatorUtils.java index f32ca11892..c706322f66 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/ValueCreatorUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/ValueCreatorUtils.java @@ -101,6 +101,10 @@ public static BMap createHTTPRecordValue(String recordTypeName) return ValueCreator.createRecordValue(ModuleUtils.getHttpPackage(), recordTypeName); } + public static Object createStatusCodeObject(String statusCodeObjName) { + return createObjectValue(ModuleUtils.getHttpPackage(), statusCodeObjName); + } + /** * Method that creates a runtime object value using the given package id and object type name. * 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 new file mode 100644 index 0000000000..ec2b7e47e9 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/http/api/nativeimpl/ExternResponseProcessor.java @@ -0,0 +1,466 @@ +package io.ballerina.stdlib.http.api.nativeimpl; + +import io.ballerina.runtime.api.Environment; +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.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.types.Field; +import io.ballerina.runtime.api.types.MapType; +import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.types.RecordType; +import io.ballerina.runtime.api.types.ReferenceType; +import io.ballerina.runtime.api.types.Type; +import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.utils.JsonUtils; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.utils.ValueUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; +import io.ballerina.stdlib.constraint.Constraints; +import io.ballerina.stdlib.http.api.HttpUtil; +import io.ballerina.stdlib.http.api.ValueCreatorUtils; +import io.netty.handler.codec.http.HttpHeaders; + +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 java.util.stream.Collectors; + +import static io.ballerina.stdlib.http.api.HttpErrorType.CLIENT_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_BINDING_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.PAYLOAD_BINDING_CLIENT_ERROR; + +public class ExternResponseProcessor { + + private static final String NO_HEADER_VALUE_ERROR_MSG = "no header value found for '%s'"; + private static final String HEADER_BINDING_FAILED_ERROR_MSG = "header binding failed for parameter: '%s'"; + private static final Map STATUS_CODE_OBJS; + static { + Map statusCodeObjectsMap = new HashMap<>(); + statusCodeObjectsMap.put("100", "StatusContinue"); + statusCodeObjectsMap.put("101", "StatusSwitchingProtocols"); + statusCodeObjectsMap.put("102", "StatusProcessing"); + statusCodeObjectsMap.put("103", "StatusEarlyHints"); + statusCodeObjectsMap.put("200", "StatusOK"); + statusCodeObjectsMap.put("201", "StatusCreated"); + statusCodeObjectsMap.put("202", "StatusAccepted"); + statusCodeObjectsMap.put("203", "StatusNonAuthoritativeInfo"); + statusCodeObjectsMap.put("204", "StatusNoContent"); + statusCodeObjectsMap.put("205", "StatusResetContent"); + statusCodeObjectsMap.put("206", "StatusPartialContent"); + statusCodeObjectsMap.put("207", "StatusMultiStatus"); + statusCodeObjectsMap.put("208", "StatusAlreadyReported"); + statusCodeObjectsMap.put("226", "StatusIMUsed"); + statusCodeObjectsMap.put("300", "StatusMultipleChoices"); + statusCodeObjectsMap.put("301", "StatusMovedPermanently"); + statusCodeObjectsMap.put("302", "StatusFound"); + statusCodeObjectsMap.put("303", "StatusSeeOther"); + statusCodeObjectsMap.put("304", "StatusNotModified"); + statusCodeObjectsMap.put("305", "StatusUseProxy"); + statusCodeObjectsMap.put("307", "StatusTemporaryRedirect"); + statusCodeObjectsMap.put("308", "StatusPermanentRedirect"); + statusCodeObjectsMap.put("400", "StatusBadRequest"); + statusCodeObjectsMap.put("401", "StatusUnauthorized"); + statusCodeObjectsMap.put("402", "StatusPaymentRequired"); + statusCodeObjectsMap.put("403", "StatusForbidden"); + statusCodeObjectsMap.put("404", "StatusNotFound"); + statusCodeObjectsMap.put("405", "StatusMethodNotAllowed"); + statusCodeObjectsMap.put("406", "StatusNotAcceptable"); + statusCodeObjectsMap.put("407", "StatusProxyAuthRequired"); + statusCodeObjectsMap.put("408", "StatusRequestTimeout"); + statusCodeObjectsMap.put("409", "StatusConflict"); + statusCodeObjectsMap.put("410", "StatusGone"); + statusCodeObjectsMap.put("411", "StatusLengthRequired"); + statusCodeObjectsMap.put("412", "StatusPreconditionFailed"); + statusCodeObjectsMap.put("413", "StatusRequestEntityTooLarge"); + statusCodeObjectsMap.put("414", "StatusRequestURITooLong"); + statusCodeObjectsMap.put("415", "StatusUnsupportedMediaType"); + statusCodeObjectsMap.put("416", "StatusRequestedRangeNotSatisfiable"); + statusCodeObjectsMap.put("417", "StatusExpectationFailed"); + statusCodeObjectsMap.put("421", "StatusMisdirectedRequest"); + statusCodeObjectsMap.put("422", "StatusUnprocessableEntity"); + statusCodeObjectsMap.put("423", "StatusLocked"); + statusCodeObjectsMap.put("424", "StatusFailedDependency"); + statusCodeObjectsMap.put("425", "StatusTooEarly"); + statusCodeObjectsMap.put("426", "StatusUpgradeRequired"); + statusCodeObjectsMap.put("428", "StatusPreconditionRequired"); + statusCodeObjectsMap.put("429", "StatusTooManyRequests"); + statusCodeObjectsMap.put("431", "StatusRequestHeaderFieldsTooLarge"); + statusCodeObjectsMap.put("451", "StatusUnavailableForLegalReasons"); + statusCodeObjectsMap.put("500", "StatusInternalServerError"); + statusCodeObjectsMap.put("501", "StatusNotImplemented"); + statusCodeObjectsMap.put("502", "StatusBadGateway"); + statusCodeObjectsMap.put("503", "StatusServiceUnavailable"); + statusCodeObjectsMap.put("504", "StatusGatewayTimeout"); + statusCodeObjectsMap.put("505", "StatusHTTPVersionNotSupported"); + statusCodeObjectsMap.put("506", "StatusVariantAlsoNegotiates"); + statusCodeObjectsMap.put("507", "StatusInsufficientStorage"); + statusCodeObjectsMap.put("508", "StatusLoopDetected"); + statusCodeObjectsMap.put("510", "StatusNotExtended"); + statusCodeObjectsMap.put("511", "StatusNetworkAuthenticationRequired"); + STATUS_CODE_OBJS = statusCodeObjectsMap; + } + + public static Object processResponse(Environment env, BObject response, BTypedesc targetType, + boolean requireValidation) { + return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env.getRuntime()); + } + + private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, + Runtime runtime) { + long responseStatusCode = response.getIntValue(StringUtils.fromString("statusCode")); + Optional statusCodeResponseType = getStatusCodeResponseType(targetType, + Long.toString(responseStatusCode)); + if (statusCodeResponseType.isPresent()) { + RecordType statusCodeRecordType = (RecordType) TypeUtils.getImpliedType(statusCodeResponseType.get()); + BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); + + String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); + if (Objects.isNull(statusCodeObjName)) { + throw HttpUtil.createHttpError("unsupported status code: " + responseStatusCode); + } + Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); + statusCodeRecord.put(StringUtils.fromString("status"), status); + + Type mediaTypeType = statusCodeRecordType.getFields().get("mediaType").getFieldType(); + Object mediaType = getMediaType(response, mediaTypeType, requireValidation); + statusCodeRecord.put(StringUtils.fromString("mediaType"), mediaType); + + Type headersType = statusCodeRecordType.getFields().get("headers").getFieldType(); + Object headerMap = getHeadersMap(response, headersType, requireValidation); + statusCodeRecord.put(StringUtils.fromString("headers"), headerMap); + + if (statusCodeRecordType.getFields().containsKey("body")) { + Type bodyType = statusCodeRecordType.getFields().get("body").getFieldType(); + Object payload = getPayload(runtime, response, bodyType, requireValidation); + if (payload instanceof BError) { + return payload; + } + statusCodeRecord.put(StringUtils.fromString("body"), payload); + } + return statusCodeRecord; + } else if ((399 < responseStatusCode) && (responseStatusCode < 600)) { + if (hasHttpResponseType(targetType)) { + return response; + } else { + return getApplicationResponseError(runtime, response); + } + } else { + try { + return getPayload(runtime, response, getAnydataType(targetType), requireValidation); + } catch (BError e) { + if (hasHttpResponseType(targetType)) { + return response; + } + return e; + } + } + } + + private static Type getAnydataType(Type targetType) { + if (targetType.isAnydata()) { + return targetType; + } + if (targetType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { + targetType = TypeUtils.getImpliedType(targetType); + } + if (targetType.getTag() == TypeTags.UNION_TAG) { + List memberTypes = ((UnionType) targetType).getMemberTypes(); + for (Type memberType : memberTypes) { + try { + return getAnydataType(memberType); + } catch (BError e) { + // ignore + } + } + } + throw HttpUtil.createHttpError("unsupported target type: " + targetType); + } + + private static Object getMediaType(BObject response, Type mediaTypeType, boolean requireValidation) { + String contentType = getContentType(response); + try { + Object convertedValue = ValueUtils.convert(Objects.nonNull(contentType) ? + StringUtils.fromString(contentType) : null, mediaTypeType); + return validateConstraints(requireValidation, convertedValue, mediaTypeType, "media-type binding failed"); + } catch (BError conversionError) { + throw HttpUtil.createHttpError("media-type binding failed", CLIENT_ERROR, conversionError); + } + } + + private static String getContentType(BObject response) { + HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData("http_headers"); + return httpHeaders.get("Content-Type"); + } + + private static Object getHeadersMap(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) { + headersImpliedType = TypeUtils.getReferredType(headersImpliedType); + } + Object headerMap; + if (headersImpliedType.getTag() == TypeTags.MAP_TAG) { + headerMap = createHeaderMap(httpHeaders, + TypeUtils.getImpliedType(((MapType) headersImpliedType).getConstrainedType())); + } else if (headersImpliedType.getTag() == TypeTags.RECORD_TYPE_TAG) { + headerMap = createHeaderRecord(httpHeaders, (RecordType) headersImpliedType); + } else { + throw HttpUtil.createHttpError("unsupported headers type: " + headersType); + } + return validateConstraints(requireValidation, headerMap, headersType, "header binding failed"); + } + + 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)) { + return Optional.of(targetType); + } + } else if (targetType instanceof UnionType) { + List memberTypes = ((UnionType) targetType).getMemberTypes(); + for (Type memberType : memberTypes) { + Optional statusCodeResponseType = getStatusCodeResponseType(memberType, statusCode); + if (statusCodeResponseType.isPresent()) { + return statusCodeResponseType; + } + } + } else if (targetType instanceof ReferenceType) { + if (!targetType.equals(TypeUtils.getImpliedType(targetType))) { + return getStatusCodeResponseType(TypeUtils.getImpliedType(targetType), statusCode); + } + } + return Optional.empty(); + } + + private static boolean isStatusCodeResponseType(Type targetType) { + return targetType instanceof ReferenceType referenceType && + TypeUtils.getImpliedType(referenceType) instanceof RecordType recordType && + recordType.getFields().containsKey("status") && + recordType.getFields().get("status").getFieldType() instanceof ObjectType; + } + + private static String getStatusCode(Type targetType) { + return ((ObjectType) ((RecordType) TypeUtils.getImpliedType(targetType)).getFields(). + get("status").getFieldType()).getFields().get("code").getFieldType().getEmptyValue().toString(); + } + + private static Object createHeaderMap(HttpHeaders httpHeaders, Type elementType) throws BError { + BMap headerMap = ValueCreator.createMapValue(); + Set headerNames = httpHeaders.names(); + for (String headerName : headerNames) { + List headerValues = httpHeaders.getAll(headerName); + try { + Object convertedValue = convertHeaderValues(headerValues, elementType); + headerMap.put(StringUtils.fromString(headerName), convertedValue); + } catch (Exception ex) { + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), null, HttpUtil.createError(ex)); + } + } + return headerMap; + } + + private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType headersType) throws BError { + Map headers = headersType.getFields(); + BMap headerMap = ValueCreator.createMapValue(); + for (Map.Entry header : headers.entrySet()) { + Field headerField = header.getValue(); + Type headerFieldType = TypeUtils.getImpliedType(headerField.getFieldType()); + + String headerName = header.getKey(); + List headerValues = getHeader(httpHeaders, headerName); + + if (Objects.isNull(headerValues) || headerValues.isEmpty()) { + if (isOptionalHeaderField(headerField) || headerFieldType.isNilable()) { + if (headerFieldType.isNilable()) { + headerMap.put(StringUtils.fromString(headerName), null); + } + continue; + } + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + String.format(NO_HEADER_VALUE_ERROR_MSG, headerName)); + } + + try { + Object convertedValue = convertHeaderValues(headerValues, headerFieldType); + headerMap.put(StringUtils.fromString(headerName), convertedValue); + } catch (Exception ex) { + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), null, HttpUtil.createError(ex)); + } + } + return headerMap; + } + + private static BArray parseHeaderValue(List header) { + List parsedValues; + try { + parsedValues = header.stream().map(JsonUtils::parse).collect(Collectors.toList()); + } catch (Exception e) { + parsedValues = header.stream().map(StringUtils::fromString).collect(Collectors.toList()); + } + return ValueCreator.createArrayValue(parsedValues.toArray(), + TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON)); + } + + static class HeaderTypeInfo { + private boolean hasString = false; + private boolean hasStringArray = false; + private boolean hasArray = false; + } + + private static HeaderTypeInfo getHeaderTypeInfo(Type headerType, HeaderTypeInfo headerTypeInfo, + boolean fromArray) { + switch (headerType.getTag()) { + case TypeTags.STRING_TAG: + if (fromArray) { + headerTypeInfo.hasStringArray = true; + } else { + headerTypeInfo.hasString = true; + } + return headerTypeInfo; + case TypeTags.ARRAY_TAG: + headerTypeInfo.hasArray = true; + Type elementType = ((ArrayType) headerType).getElementType(); + return getHeaderTypeInfo(elementType, headerTypeInfo, true); + case TypeTags.UNION_TAG: + List memberTypes = ((UnionType) headerType).getMemberTypes(); + for (Type memberType : memberTypes) { + headerTypeInfo = getHeaderTypeInfo(memberType, headerTypeInfo, false); + } + return headerTypeInfo; + case TypeTags.TYPE_REFERENCED_TYPE_TAG: + return getHeaderTypeInfo(TypeUtils.getImpliedType(headerType), headerTypeInfo, false); + default: + return headerTypeInfo; + } + } + + private static Object convertHeaderValues(List headerValues, Type headerType) { + HeaderTypeInfo headerTypeInfo = getHeaderTypeInfo(headerType, new HeaderTypeInfo(), false); + if (headerTypeInfo.hasString) { + return StringUtils.fromString(headerValues.get(0)); + } else if (headerTypeInfo.hasStringArray) { + return StringUtils.fromStringArray(headerValues.toArray(new String[0])); + } else { + BArray parsedValues = parseHeaderValue(headerValues); + if (headerTypeInfo.hasArray) { + try { + return ValueUtils.convert(parsedValues, headerType); + } catch (BError e) { + return ValueUtils.convert(parsedValues.get(0), headerType); + } + } else { + return ValueUtils.convert(parsedValues.get(0), headerType); + } + } + } + + 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); + } + + private static List getHeader(HttpHeaders httpHeaders, String headerName) { + return httpHeaders.getAll(headerName); + } + + private static Object validateConstraints(boolean requireValidation, Object convertedValue, Type type, + String errorMsg) { + if (requireValidation) { + Object result = Constraints.validate(convertedValue, ValueCreator.createTypedescValue(type)); + if (result instanceof BError) { + String message = errorMsg + ": " + HttpUtil.getPrintableErrorMsg((BError) result); + throw HttpUtil.createHttpError(message, CLIENT_ERROR); + } + } + 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); + Callback returnCallback = new Callback() { + @Override + public void notifySuccess(Object result) { + payload[0] = result; + countDownLatch.countDown(); + } + + @Override + public void notifyFailure(BError result) { + payload[0] = result; + countDownLatch.countDown(); + } + }; + Object[] paramFeed = new Object[4]; + paramFeed[0] = ValueCreator.createTypedescValue(payloadType); + paramFeed[1] = true; + paramFeed[2] = requireValidation; + paramFeed[3] = true; + runtime.invokeMethodAsyncSequentially(response, "performDataBinding", null, + ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ANY, + paramFeed); + try { + countDownLatch.await(); + return payload[0]; + } catch (InterruptedException exception) { + return HttpUtil.createHttpError("payload binding failed", CLIENT_ERROR, HttpUtil.createError(exception)); + } + } + + public static Object getApplicationResponseError(Runtime runtime, BObject response) { + final Object[] clientError = new Object[1]; + CountDownLatch countDownLatch = new CountDownLatch(1); + Callback returnCallback = new Callback() { + @Override + public void notifySuccess(Object result) { + clientError[0] = result; + countDownLatch.countDown(); + } + + @Override + public void notifyFailure(BError result) { + clientError[0] = result; + countDownLatch.countDown(); + } + }; + runtime.invokeMethodAsyncSequentially(response, "getApplicationResponseError", null, + ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ERROR); + try { + countDownLatch.await(); + return clientError[0]; + } catch (InterruptedException exception) { + return HttpUtil.createHttpError("http:ApplicationResponseError creation failed", + PAYLOAD_BINDING_CLIENT_ERROR, HttpUtil.createError(exception)); + } + } +} From 25512b3da796e9145c8802d07228d4cc3748285a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Sun, 24 Mar 2024 07:50:03 +0530 Subject: [PATCH 03/30] Refactor the logic for getting anydata types --- .../nativeimpl/ExternResponseProcessor.java | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 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 ec2b7e47e9..6d5e63d1b9 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 @@ -31,6 +31,7 @@ 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; @@ -172,23 +173,34 @@ private static Object getResponseWithType(BObject response, Type targetType, boo } private static Type getAnydataType(Type targetType) { - if (targetType.isAnydata()) { - return targetType; + List anydataTypes = extractAnydataTypes(targetType, new ArrayList<>()); + if (anydataTypes.isEmpty()) { + throw HttpUtil.createHttpError("unsupported target type: " + targetType); + } else if (anydataTypes.size() == 1) { + return anydataTypes.get(0); + } else { + return TypeCreator.createUnionType(anydataTypes); } - if (targetType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { - targetType = TypeUtils.getImpliedType(targetType); + } + + private static List extractAnydataTypes(Type targetType, List anydataTypes) { + if (targetType.isAnydata()) { + anydataTypes.add(targetType); + return anydataTypes; } - if (targetType.getTag() == TypeTags.UNION_TAG) { - List memberTypes = ((UnionType) targetType).getMemberTypes(); - for (Type memberType : memberTypes) { - try { - return getAnydataType(memberType); - } catch (BError e) { - // ignore + + 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; } - throw HttpUtil.createHttpError("unsupported target type: " + targetType); } private static Object getMediaType(BObject response, Type mediaTypeType, boolean requireValidation) { From 5c2ef00efcda0b2f74555b82bb48adecf4958eba Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 27 Mar 2024 08:47:19 +0530 Subject: [PATCH 04/30] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index fe6be94a7d..fbc3b557cc 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" authors = ["Ballerina"] keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" @@ -16,8 +16,8 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "http-native" -version = "2.10.12" -path = "../native/build/libs/http-native-2.10.12.jar" +version = "2.11.0" +path = "../native/build/libs/http-native-2.11.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index d0f2684740..763ca3e699 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "http-compiler-plugin" class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/http-compiler-plugin-2.10.12.jar" +path = "../compiler-plugin/build/libs/http-compiler-plugin-2.11.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d37f655f67..b046a5e98a 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -25,7 +25,7 @@ modules = [ [[package]] org = "ballerina" name = "cache" -version = "3.7.0" +version = "3.7.1" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "jballerina.java"}, @@ -76,7 +76,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, @@ -283,7 +283,7 @@ modules = [ [[package]] org = "ballerina" name = "observe" -version = "1.2.0" +version = "1.2.2" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] From 2fa4541c0e67ebb6296e24eb09092c55a603c09f Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 27 Mar 2024 08:56:59 +0530 Subject: [PATCH 05/30] [Automated] Update the native jar versions --- ballerina-tests/http-advanced-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-advanced-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-client-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-client-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-dispatching-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-dispatching-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-interceptor-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-interceptor-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-misc-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-misc-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-resiliency-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-resiliency-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-security-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-security-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-service-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-service-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-test-common/Ballerina.toml | 2 +- ballerina-tests/http-test-common/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Ballerina.toml | 6 +++--- ballerina-tests/http2-tests/Dependencies.toml | 4 ++-- 20 files changed, 47 insertions(+), 47 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Ballerina.toml b/ballerina-tests/http-advanced-tests/Ballerina.toml index fe114a34fd..dcf2db347b 100644 --- a/ballerina-tests/http-advanced-tests/Ballerina.toml +++ b/ballerina-tests/http-advanced-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_advanced_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index de2ec81227..dbabcfe41a 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -105,7 +105,7 @@ modules = [ [[package]] org = "ballerina" name = "http_advanced_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "file"}, @@ -125,7 +125,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-client-tests/Ballerina.toml b/ballerina-tests/http-client-tests/Ballerina.toml index e83a313f7b..77d1aa1317 100644 --- a/ballerina-tests/http-client-tests/Ballerina.toml +++ b/ballerina-tests/http-client-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_client_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 31605dc3df..a1a26726b9 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_client_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-dispatching-tests/Ballerina.toml b/ballerina-tests/http-dispatching-tests/Ballerina.toml index 7429ed0d48..fd0e652876 100644 --- a/ballerina-tests/http-dispatching-tests/Ballerina.toml +++ b/ballerina-tests/http-dispatching-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_dispatching_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 17978608a8..10efd222e7 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_dispatching_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -124,7 +124,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-interceptor-tests/Ballerina.toml b/ballerina-tests/http-interceptor-tests/Ballerina.toml index aef2f4e233..046773fd38 100644 --- a/ballerina-tests/http-interceptor-tests/Ballerina.toml +++ b/ballerina-tests/http-interceptor-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_interceptor_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 98035d8644..ef7940f0f3 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_interceptor_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -115,7 +115,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-misc-tests/Ballerina.toml b/ballerina-tests/http-misc-tests/Ballerina.toml index a475ff6946..415cb314c4 100644 --- a/ballerina-tests/http-misc-tests/Ballerina.toml +++ b/ballerina-tests/http-misc-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_misc_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 913fdbe5d6..b968cc9492 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_misc_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -118,7 +118,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-resiliency-tests/Ballerina.toml b/ballerina-tests/http-resiliency-tests/Ballerina.toml index c2c7d04eec..3f12777866 100644 --- a/ballerina-tests/http-resiliency-tests/Ballerina.toml +++ b/ballerina-tests/http-resiliency-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_resiliency_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index ad0ddfa58a..1686f05dfc 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_resiliency_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -116,7 +116,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-security-tests/Ballerina.toml b/ballerina-tests/http-security-tests/Ballerina.toml index 2809ceedbe..3f2b2243ca 100644 --- a/ballerina-tests/http-security-tests/Ballerina.toml +++ b/ballerina-tests/http-security-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_security_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 5616ba9792..71795e2dfd 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_security_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "http"}, @@ -120,7 +120,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-service-tests/Ballerina.toml b/ballerina-tests/http-service-tests/Ballerina.toml index e78b3ed25d..9ec9cd7ddd 100644 --- a/ballerina-tests/http-service-tests/Ballerina.toml +++ b/ballerina-tests/http-service-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_service_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index f59427d5ab..1247dfd340 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_service_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-test-common/Ballerina.toml b/ballerina-tests/http-test-common/Ballerina.toml index c138dd1d53..e7b61366b7 100644 --- a/ballerina-tests/http-test-common/Ballerina.toml +++ b/ballerina-tests/http-test-common/Ballerina.toml @@ -1,4 +1,4 @@ [package] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index 5e193415ee..020c6cfeac 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.8.0" [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "mime"}, diff --git a/ballerina-tests/http2-tests/Ballerina.toml b/ballerina-tests/http2-tests/Ballerina.toml index 7212b41fd1..f4c4980194 100644 --- a/ballerina-tests/http2-tests/Ballerina.toml +++ b/ballerina-tests/http2-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http2_tests" -version = "2.10.12" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.12" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.12.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index b83cb7e6b2..30a219ba68 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http2_tests" -version = "2.10.12" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, From 1eadf7bb24b62acce7dc90ca99a6202028a4ab8d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 28 Mar 2024 10:20:53 +0530 Subject: [PATCH 06/30] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 761542514d..8bf7f22bc8 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.11.0" authors = ["Ballerina"] keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" @@ -16,8 +16,8 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "http-native" -version = "2.10.13" -path = "../native/build/libs/http-native-2.10.13-SNAPSHOT.jar" +version = "2.11.0" +path = "../native/build/libs/http-native-2.11.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index ec52292d2b..763ca3e699 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "http-compiler-plugin" class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/http-compiler-plugin-2.10.13-SNAPSHOT.jar" +path = "../compiler-plugin/build/libs/http-compiler-plugin-2.11.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 4ef26fb408..b046a5e98a 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -76,7 +76,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, From 929b1ed38d96f1e3ecaf4e9da55b5cf40473aedf Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 28 Mar 2024 10:41:07 +0530 Subject: [PATCH 07/30] [Automated] Update the native jar versions --- ballerina-tests/http-advanced-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-advanced-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-client-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-client-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-dispatching-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-dispatching-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-interceptor-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-interceptor-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-misc-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-misc-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-resiliency-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-resiliency-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-security-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-security-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-service-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-service-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-test-common/Ballerina.toml | 2 +- ballerina-tests/http-test-common/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Ballerina.toml | 6 +++--- ballerina-tests/http2-tests/Dependencies.toml | 6 +++--- 20 files changed, 56 insertions(+), 56 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Ballerina.toml b/ballerina-tests/http-advanced-tests/Ballerina.toml index 6189f1ab9e..dcf2db347b 100644 --- a/ballerina-tests/http-advanced-tests/Ballerina.toml +++ b/ballerina-tests/http-advanced-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_advanced_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 4a5d29e655..dbabcfe41a 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -72,7 +72,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -105,7 +105,7 @@ modules = [ [[package]] org = "ballerina" name = "http_advanced_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "file"}, @@ -125,7 +125,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-client-tests/Ballerina.toml b/ballerina-tests/http-client-tests/Ballerina.toml index afee86a497..77d1aa1317 100644 --- a/ballerina-tests/http-client-tests/Ballerina.toml +++ b/ballerina-tests/http-client-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_client_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 4f21f0e96a..a1a26726b9 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_client_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-dispatching-tests/Ballerina.toml b/ballerina-tests/http-dispatching-tests/Ballerina.toml index 14d64f0d70..fd0e652876 100644 --- a/ballerina-tests/http-dispatching-tests/Ballerina.toml +++ b/ballerina-tests/http-dispatching-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_dispatching_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index fd72573961..10efd222e7 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_dispatching_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -124,7 +124,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-interceptor-tests/Ballerina.toml b/ballerina-tests/http-interceptor-tests/Ballerina.toml index e06cc6b545..046773fd38 100644 --- a/ballerina-tests/http-interceptor-tests/Ballerina.toml +++ b/ballerina-tests/http-interceptor-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_interceptor_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index f72b10f0ff..ef7940f0f3 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_interceptor_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -115,7 +115,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-misc-tests/Ballerina.toml b/ballerina-tests/http-misc-tests/Ballerina.toml index be9c89cba1..415cb314c4 100644 --- a/ballerina-tests/http-misc-tests/Ballerina.toml +++ b/ballerina-tests/http-misc-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_misc_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 6df15e7b92..b968cc9492 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_misc_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -118,7 +118,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-resiliency-tests/Ballerina.toml b/ballerina-tests/http-resiliency-tests/Ballerina.toml index 8efb9afbe5..3f12777866 100644 --- a/ballerina-tests/http-resiliency-tests/Ballerina.toml +++ b/ballerina-tests/http-resiliency-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_resiliency_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index fb3738df82..1686f05dfc 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_resiliency_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -116,7 +116,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-security-tests/Ballerina.toml b/ballerina-tests/http-security-tests/Ballerina.toml index b5bdd5f6af..3f2b2243ca 100644 --- a/ballerina-tests/http-security-tests/Ballerina.toml +++ b/ballerina-tests/http-security-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_security_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 21927f472e..71795e2dfd 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_security_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "http"}, @@ -120,7 +120,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-service-tests/Ballerina.toml b/ballerina-tests/http-service-tests/Ballerina.toml index 1b0c1bd7bd..9ec9cd7ddd 100644 --- a/ballerina-tests/http-service-tests/Ballerina.toml +++ b/ballerina-tests/http-service-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_service_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 5047ee4403..1247dfd340 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_service_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, diff --git a/ballerina-tests/http-test-common/Ballerina.toml b/ballerina-tests/http-test-common/Ballerina.toml index 259ae645ae..e7b61366b7 100644 --- a/ballerina-tests/http-test-common/Ballerina.toml +++ b/ballerina-tests/http-test-common/Ballerina.toml @@ -1,4 +1,4 @@ [package] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index 3dbce05f84..020c6cfeac 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.8.0" [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "mime"}, diff --git a/ballerina-tests/http2-tests/Ballerina.toml b/ballerina-tests/http2-tests/Ballerina.toml index 23fcde7ac1..f4c4980194 100644 --- a/ballerina-tests/http2-tests/Ballerina.toml +++ b/ballerina-tests/http2-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http2_tests" -version = "2.10.13" +version = "2.11.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.10.13" +version = "2.11.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.10.13-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.11.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 752f811de1..30a219ba68 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.13" +version = "2.10.12" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http2_tests" -version = "2.10.13" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.10.13" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, From 52326d3445addf5bb48e425c32179786038b55ce Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 3 Apr 2024 12:24:46 +0530 Subject: [PATCH 08/30] Fix code quality issues --- .../stdlib/http/api/HttpConstants.java | 3 + .../nativeimpl/ExternResponseProcessor.java | 127 ++++++++++-------- 2 files changed, 76 insertions(+), 54 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 a2bb57fbb2..97c7895ca9 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 @@ -294,6 +294,9 @@ public final class HttpConstants { //StatusCodeResponse struct field names public static final String STATUS_CODE_RESPONSE_BODY_FIELD = "body"; public static final String STATUS_CODE_RESPONSE_STATUS_FIELD = "status"; + public static final String STATUS_CODE_RESPONSE_STATUS_CODE_FIELD = "code"; + public static final String STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD = "mediaType"; + public static final String STATUS_CODE_RESPONSE_HEADERS_FIELD = "headers"; //PushPromise struct field names public static final BString PUSH_PROMISE_PATH_FIELD = StringUtils.fromString("path"); 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 6d5e63d1b9..78fdd3f9e7 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 @@ -32,6 +32,7 @@ import io.netty.handler.codec.http.HttpHeaders; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,17 +40,23 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.stream.Collectors; +import static io.ballerina.stdlib.http.api.HttpConstants.HTTP_HEADERS; +import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_BODY_FIELD; +import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_HEADERS_FIELD; +import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD; +import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_STATUS_CODE_FIELD; +import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_STATUS_FIELD; import static io.ballerina.stdlib.http.api.HttpErrorType.CLIENT_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_BINDING_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.PAYLOAD_BINDING_CLIENT_ERROR; -public class ExternResponseProcessor { +public final class ExternResponseProcessor { private static final String NO_HEADER_VALUE_ERROR_MSG = "no header value found for '%s'"; private static final String HEADER_BINDING_FAILED_ERROR_MSG = "header binding failed for parameter: '%s'"; private static final Map STATUS_CODE_OBJS; + static { Map statusCodeObjectsMap = new HashMap<>(); statusCodeObjectsMap.put("100", "StatusContinue"); @@ -116,6 +123,9 @@ public class ExternResponseProcessor { STATUS_CODE_OBJS = statusCodeObjectsMap; } + private ExternResponseProcessor() { + } + public static Object processResponse(Environment env, BObject response, BTypedesc targetType, boolean requireValidation) { return getResponseWithType(response, targetType.getDescribingType(), requireValidation, env.getRuntime()); @@ -126,50 +136,57 @@ private static Object getResponseWithType(BObject response, Type targetType, boo long responseStatusCode = response.getIntValue(StringUtils.fromString("statusCode")); Optional statusCodeResponseType = getStatusCodeResponseType(targetType, Long.toString(responseStatusCode)); - if (statusCodeResponseType.isPresent()) { - RecordType statusCodeRecordType = (RecordType) TypeUtils.getImpliedType(statusCodeResponseType.get()); - BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); - - String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); - if (Objects.isNull(statusCodeObjName)) { - throw HttpUtil.createHttpError("unsupported status code: " + responseStatusCode); - } - Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); - statusCodeRecord.put(StringUtils.fromString("status"), status); - - Type mediaTypeType = statusCodeRecordType.getFields().get("mediaType").getFieldType(); - Object mediaType = getMediaType(response, mediaTypeType, requireValidation); - statusCodeRecord.put(StringUtils.fromString("mediaType"), mediaType); - - Type headersType = statusCodeRecordType.getFields().get("headers").getFieldType(); - Object headerMap = getHeadersMap(response, headersType, requireValidation); - statusCodeRecord.put(StringUtils.fromString("headers"), headerMap); - - if (statusCodeRecordType.getFields().containsKey("body")) { - Type bodyType = statusCodeRecordType.getFields().get("body").getFieldType(); - Object payload = getPayload(runtime, response, bodyType, requireValidation); - if (payload instanceof BError) { - return payload; - } - statusCodeRecord.put(StringUtils.fromString("body"), payload); - } - return statusCodeRecord; + if (statusCodeResponseType.isPresent() && + TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { + return generateStatusCodeResponseType(response, requireValidation, runtime, 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; - } else { - return getApplicationResponseError(runtime, response); } - } else { - try { - return getPayload(runtime, response, getAnydataType(targetType), requireValidation); - } catch (BError e) { - if (hasHttpResponseType(targetType)) { - return response; - } - return e; + return e; + } + } + + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Runtime runtime, + RecordType statusCodeRecordType, long responseStatusCode) { + BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); + + String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); + if (Objects.isNull(statusCodeObjName)) { + throw HttpUtil.createHttpError("unsupported status code: " + responseStatusCode); + } + Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_STATUS_FIELD), status); + + Type mediaTypeType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD).getFieldType(); + Object mediaType = getMediaType(response, mediaTypeType, requireValidation); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); + + Type headersType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_HEADERS_FIELD).getFieldType(); + Object headerMap = getHeadersMap(response, headersType, requireValidation); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); + + if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { + Type bodyType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_BODY_FIELD).getFieldType(); + Object payload = getPayload(runtime, response, bodyType, requireValidation); + if (payload instanceof BError) { + return payload; } + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_BODY_FIELD), payload); } + return statusCodeRecord; } private static Type getAnydataType(Type targetType) { @@ -215,12 +232,12 @@ private static Object getMediaType(BObject response, Type mediaTypeType, boolean } private static String getContentType(BObject response) { - HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData("http_headers"); + HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData(HTTP_HEADERS); return httpHeaders.get("Content-Type"); } private static Object getHeadersMap(BObject response, Type headersType, boolean requireValidation) { - HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData("http_headers"); + HttpHeaders httpHeaders = (HttpHeaders) response.getNativeData(HTTP_HEADERS); Type headersImpliedType = TypeUtils.getImpliedType(headersType); if (headersImpliedType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { headersImpliedType = TypeUtils.getReferredType(headersImpliedType); @@ -252,18 +269,17 @@ private static Optional getStatusCodeResponseType(Type targetType, String if (getStatusCode(targetType).equals(statusCode)) { return Optional.of(targetType); } - } else if (targetType instanceof UnionType) { - List memberTypes = ((UnionType) targetType).getMemberTypes(); + } else if (targetType instanceof UnionType unionType) { + List memberTypes = unionType.getMemberTypes(); for (Type memberType : memberTypes) { Optional statusCodeResponseType = getStatusCodeResponseType(memberType, statusCode); if (statusCodeResponseType.isPresent()) { return statusCodeResponseType; } } - } else if (targetType instanceof ReferenceType) { - if (!targetType.equals(TypeUtils.getImpliedType(targetType))) { + } else if (targetType instanceof ReferenceType + && (!targetType.equals(TypeUtils.getImpliedType(targetType)))) { return getStatusCodeResponseType(TypeUtils.getImpliedType(targetType), statusCode); - } } return Optional.empty(); } @@ -271,13 +287,14 @@ private static Optional getStatusCodeResponseType(Type targetType, String private static boolean isStatusCodeResponseType(Type targetType) { return targetType instanceof ReferenceType referenceType && TypeUtils.getImpliedType(referenceType) instanceof RecordType recordType && - recordType.getFields().containsKey("status") && - recordType.getFields().get("status").getFieldType() instanceof ObjectType; + recordType.getFields().containsKey(STATUS_CODE_RESPONSE_STATUS_FIELD) && + recordType.getFields().get(STATUS_CODE_RESPONSE_STATUS_FIELD).getFieldType() instanceof ObjectType; } private static String getStatusCode(Type targetType) { return ((ObjectType) ((RecordType) TypeUtils.getImpliedType(targetType)).getFields(). - get("status").getFieldType()).getFields().get("code").getFieldType().getEmptyValue().toString(); + get(STATUS_CODE_RESPONSE_STATUS_FIELD).getFieldType()).getFields(). + get(STATUS_CODE_RESPONSE_STATUS_CODE_FIELD).getFieldType().getEmptyValue().toString(); } private static Object createHeaderMap(HttpHeaders httpHeaders, Type elementType) throws BError { @@ -331,9 +348,9 @@ private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType hea private static BArray parseHeaderValue(List header) { List parsedValues; try { - parsedValues = header.stream().map(JsonUtils::parse).collect(Collectors.toList()); + parsedValues = header.stream().map(JsonUtils::parse).toList(); } catch (Exception e) { - parsedValues = header.stream().map(StringUtils::fromString).collect(Collectors.toList()); + parsedValues = Collections.singletonList(header.stream().map(StringUtils::fromString)); } return ValueCreator.createArrayValue(parsedValues.toArray(), TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON)); @@ -408,8 +425,8 @@ private static Object validateConstraints(boolean requireValidation, Object conv String errorMsg) { if (requireValidation) { Object result = Constraints.validate(convertedValue, ValueCreator.createTypedescValue(type)); - if (result instanceof BError) { - String message = errorMsg + ": " + HttpUtil.getPrintableErrorMsg((BError) result); + if (result instanceof BError bError) { + String message = errorMsg + ": " + HttpUtil.getPrintableErrorMsg(bError); throw HttpUtil.createHttpError(message, CLIENT_ERROR); } } @@ -445,6 +462,7 @@ public void notifyFailure(BError result) { countDownLatch.await(); return payload[0]; } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); return HttpUtil.createHttpError("payload binding failed", CLIENT_ERROR, HttpUtil.createError(exception)); } } @@ -471,6 +489,7 @@ public void notifyFailure(BError result) { countDownLatch.await(); return clientError[0]; } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); return HttpUtil.createHttpError("http:ApplicationResponseError creation failed", PAYLOAD_BINDING_CLIENT_ERROR, HttpUtil.createError(exception)); } From 222cb205fbe86ff7f1bf405faddff3f4efed5dea Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 3 Apr 2024 17:18:04 +0530 Subject: [PATCH 09/30] Update resiliency clients --- ballerina/http_client_endpoint.bal | 3 +- ballerina/resiliency_failover_client.bal | 64 ++++++++++------ ballerina/resiliency_load_balance_client.bal | 78 +++++++++++++------- 3 files changed, 92 insertions(+), 53 deletions(-) diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 3da0fe3e03..45760fb3ba 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -733,8 +733,7 @@ isolated function createResponseError(int statusCode, string reasonPhrase, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_POST); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -144,14 +146,16 @@ public client isolated class FailoverClient { } external; private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PUT); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -189,14 +193,16 @@ public client isolated class FailoverClient { } external; private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_PATCH); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -234,14 +240,16 @@ public client isolated class FailoverClient { } external; private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performFailoverAction(path, req, HTTP_DELETE); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -303,13 +311,15 @@ public client isolated class FailoverClient { } external; private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performFailoverAction(path, req, HTTP_GET); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -342,13 +352,15 @@ public client isolated class FailoverClient { } external; private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performFailoverAction(path, req, HTTP_OPTIONS); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -372,14 +384,16 @@ public client isolated class FailoverClient { private isolated function processExecute(string httpVerb, string path, RequestMessage message, TargetType targetType, string? mediaType, map? headers) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performExecuteAction(path, req, httpVerb); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } else { panic error ClientError("invalid response type received"); } @@ -398,12 +412,14 @@ public client isolated class FailoverClient { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { var result = self.performFailoverAction(path, request, HTTP_FORWARD); if result is HttpFuture { return getInvalidTypeError(); - } else if result is Response || result is ClientError { - return processResponse(result, targetType, self.requireValidation); + } else if result is ClientError { + return result; + } else if result is Response { + return externProcessResponse(result, targetType, self.requireValidation); } 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 790c18ad4c..9d6d27cd94 100644 --- a/ballerina/resiliency_load_balance_client.bal +++ b/ballerina/resiliency_load_balance_client.bal @@ -92,11 +92,14 @@ public client isolated class LoadBalanceClient { } external; private isolated function processPost(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_POST); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The PUT resource function implementation of the LoadBalancer Connector. @@ -129,13 +132,16 @@ public client isolated class LoadBalanceClient { returns targetType|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" } external; - - private isolated function processPut(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + + private isolated function processPut(string path, RequestMessage message, TargetType targetType, + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PUT); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The PATCH resource function implementation of the LoadBalancer Connector. @@ -168,13 +174,16 @@ public client isolated class LoadBalanceClient { returns targetType|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" } external; - - private isolated function processPatch(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + + private isolated function processPatch(string path, RequestMessage message, TargetType targetType, + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_PATCH); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The DELETE resource function implementation of the LoadBalancer Connector. @@ -207,13 +216,16 @@ public client isolated class LoadBalanceClient { returns targetType|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" } external; - - private isolated function processDelete(string path, RequestMessage message, TargetType targetType, - string? mediaType, map? headers) returns Response|anydata|ClientError { + + private isolated function processDelete(string path, RequestMessage message, TargetType targetType, + string? mediaType, map? headers) returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceAction(path, req, HTTP_DELETE); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The HEAD resource function implementation of the LoadBalancer Connector. @@ -253,7 +265,7 @@ public client isolated class LoadBalanceClient { } external; # The GET remote function implementation of the LoadBalancer Connector. - # + # # + path - Request path # + headers - The entity headers # + targetType - HTTP response or `anydata`, which is expected to be returned after data binding @@ -263,12 +275,15 @@ public client isolated class LoadBalanceClient { returns targetType|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" } external; - + private isolated function processGet(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_GET); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The OPTIONS resource function implementation of the LoadBalancer Connector. @@ -296,12 +311,15 @@ public client isolated class LoadBalanceClient { returns targetType|ClientError = @java:Method { 'class: "io.ballerina.stdlib.http.api.client.actions.HttpClientAction" } external; - + private isolated function processOptions(string path, map? headers, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { Request req = buildRequestWithHeaders(headers); var result = self.performLoadBalanceAction(path, req, HTTP_OPTIONS); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The EXECUTE remote function implementation of the LoadBalancer Connector. @@ -319,14 +337,17 @@ public client isolated class LoadBalanceClient { 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, - TargetType targetType, string? mediaType, map? headers) - returns Response|anydata|ClientError { + TargetType targetType, string? mediaType, map? headers) + returns Response|StatusCodeResponse|anydata|ClientError { Request req = check buildRequest(message, mediaType); populateOptions(req, mediaType, headers); var result = self.performLoadBalanceExecuteAction(path, req, httpVerb); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The FORWARD remote function implementation of the LoadBalancer Connector. @@ -342,9 +363,12 @@ public client isolated class LoadBalanceClient { } external; private isolated function processForward(string path, Request request, TargetType targetType) - returns Response|anydata|ClientError { + returns Response|StatusCodeResponse|anydata|ClientError { var result = self.performLoadBalanceAction(path, request, HTTP_FORWARD); - return processResponse(result, targetType, self.requireValidation); + if result is ClientError { + return result; + } + return externProcessResponse(result, targetType, self.requireValidation); } # The submit implementation of the LoadBalancer Connector. From 27424153f574e27f91d459f79e37377584fa6201 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 3 Apr 2024 17:19:18 +0530 Subject: [PATCH 10/30] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index b046a5e98a..e8516252c3 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -39,7 +39,7 @@ modules = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] @@ -50,7 +50,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} From e1454b71456e262252b148f50b5368d849faa358 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 3 Apr 2024 17:29:33 +0530 Subject: [PATCH 11/30] [Automated] Update the native jar versions --- ballerina-tests/http-advanced-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-client-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-dispatching-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-interceptor-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-misc-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-resiliency-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-security-tests/Dependencies.toml | 6 +++--- ballerina-tests/http-service-tests/Dependencies.toml | 6 +++--- ballerina-tests/http2-tests/Dependencies.toml | 6 +++--- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index dbabcfe41a..029e4865b5 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -72,7 +72,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index a1a26726b9..631480b290 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -47,7 +47,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 10efd222e7..e371fe4128 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -47,7 +47,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index ef7940f0f3..39b8bac734 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index b968cc9492..d7d6f93a37 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index 1686f05dfc..c7a4dc41ab 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 71795e2dfd..a3240ec146 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -38,7 +38,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -47,7 +47,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 1247dfd340..db72c8140e 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 30a219ba68..e566d25184 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -35,7 +35,7 @@ dependencies = [ [[package]] org = "ballerina" name = "constraint" -version = "1.4.0" +version = "1.5.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.5.0" +version = "2.6.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.10.12" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, From cb1bb75b795b902470762171b90dd6ebd21c11a2 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 3 Apr 2024 18:56:30 +0530 Subject: [PATCH 12/30] Remove the unused method --- ballerina/http_client_endpoint.bal | 31 ------------------------------ 1 file changed, 31 deletions(-) diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index 45760fb3ba..b69ebec5b3 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -644,37 +644,6 @@ isolated function createDefaultClient(string url, ClientConfiguration configurat return createHttpSecureClient(url, configuration); } -isolated function processResponse(Response|ClientError response, TargetType targetType, boolean requireValidation) - 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 getPayload(Response response) returns anydata|error { string|error contentTypeValue = response.getHeader(CONTENT_TYPE); string value = ""; From 9a9856e11baabfb0c4cc106a6ad7ed6cab06aefb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 07:32:45 +0530 Subject: [PATCH 13/30] Fix issues in the extern response processor --- .../nativeimpl/ExternResponseProcessor.java | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 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 78fdd3f9e7..101eb8aff7 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 @@ -66,7 +66,7 @@ public final class ExternResponseProcessor { statusCodeObjectsMap.put("200", "StatusOK"); statusCodeObjectsMap.put("201", "StatusCreated"); statusCodeObjectsMap.put("202", "StatusAccepted"); - statusCodeObjectsMap.put("203", "StatusNonAuthoritativeInfo"); + statusCodeObjectsMap.put("203", "StatusNonAuthoritativeInformation"); statusCodeObjectsMap.put("204", "StatusNoContent"); statusCodeObjectsMap.put("205", "StatusResetContent"); statusCodeObjectsMap.put("206", "StatusPartialContent"); @@ -88,16 +88,16 @@ public final class ExternResponseProcessor { statusCodeObjectsMap.put("404", "StatusNotFound"); statusCodeObjectsMap.put("405", "StatusMethodNotAllowed"); statusCodeObjectsMap.put("406", "StatusNotAcceptable"); - statusCodeObjectsMap.put("407", "StatusProxyAuthRequired"); + statusCodeObjectsMap.put("407", "StatusProxyAuthenticationRequired"); statusCodeObjectsMap.put("408", "StatusRequestTimeout"); statusCodeObjectsMap.put("409", "StatusConflict"); statusCodeObjectsMap.put("410", "StatusGone"); statusCodeObjectsMap.put("411", "StatusLengthRequired"); statusCodeObjectsMap.put("412", "StatusPreconditionFailed"); - statusCodeObjectsMap.put("413", "StatusRequestEntityTooLarge"); - statusCodeObjectsMap.put("414", "StatusRequestURITooLong"); + statusCodeObjectsMap.put("413", "StatusPayloadTooLarge"); + statusCodeObjectsMap.put("414", "StatusUriTooLong"); statusCodeObjectsMap.put("415", "StatusUnsupportedMediaType"); - statusCodeObjectsMap.put("416", "StatusRequestedRangeNotSatisfiable"); + statusCodeObjectsMap.put("416", "StatusRangeNotSatisfiable"); statusCodeObjectsMap.put("417", "StatusExpectationFailed"); statusCodeObjectsMap.put("421", "StatusMisdirectedRequest"); statusCodeObjectsMap.put("422", "StatusUnprocessableEntity"); @@ -108,13 +108,13 @@ public final class ExternResponseProcessor { statusCodeObjectsMap.put("428", "StatusPreconditionRequired"); statusCodeObjectsMap.put("429", "StatusTooManyRequests"); statusCodeObjectsMap.put("431", "StatusRequestHeaderFieldsTooLarge"); - statusCodeObjectsMap.put("451", "StatusUnavailableForLegalReasons"); + statusCodeObjectsMap.put("451", "StatusUnavailableDueToLegalReasons"); statusCodeObjectsMap.put("500", "StatusInternalServerError"); statusCodeObjectsMap.put("501", "StatusNotImplemented"); statusCodeObjectsMap.put("502", "StatusBadGateway"); statusCodeObjectsMap.put("503", "StatusServiceUnavailable"); statusCodeObjectsMap.put("504", "StatusGatewayTimeout"); - statusCodeObjectsMap.put("505", "StatusHTTPVersionNotSupported"); + statusCodeObjectsMap.put("505", "StatusHttpVersionNotSupported"); statusCodeObjectsMap.put("506", "StatusVariantAlsoNegotiates"); statusCodeObjectsMap.put("507", "StatusInsufficientStorage"); statusCodeObjectsMap.put("508", "StatusLoopDetected"); @@ -167,20 +167,16 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r if (Objects.isNull(statusCodeObjName)) { throw HttpUtil.createHttpError("unsupported status code: " + responseStatusCode); } - Object status = ValueCreatorUtils.createStatusCodeObject(statusCodeObjName); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_STATUS_FIELD), status); - Type mediaTypeType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD).getFieldType(); - Object mediaType = getMediaType(response, mediaTypeType, requireValidation); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); + populateStatusCodeObject(statusCodeObjName, statusCodeRecord); + populatedHeaders(response, requireValidation, statusCodeRecordType, statusCodeRecord); - Type headersType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_HEADERS_FIELD).getFieldType(); - Object headerMap = getHeadersMap(response, headersType, requireValidation); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); + if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD)) { + populateMediaType(response, requireValidation, statusCodeRecordType, statusCodeRecord); + } if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { - Type bodyType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_BODY_FIELD).getFieldType(); - Object payload = getPayload(runtime, response, bodyType, requireValidation); + Object payload = getBody(response, requireValidation, runtime, statusCodeRecordType); if (payload instanceof BError) { return payload; } @@ -189,6 +185,31 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r return statusCodeRecord; } + 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 void populatedHeaders(BObject response, boolean requireValidation, RecordType statusCodeRecordType, + BMap statusCodeRecord) { + Type headersType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_HEADERS_FIELD).getFieldType(); + Object headerMap = getHeadersMap(response, headersType, requireValidation); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); + } + + private static void populateMediaType(BObject response, boolean requireValidation, RecordType statusCodeRecordType, + BMap statusCodeRecord) { + Type mediaTypeType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD).getFieldType(); + Object mediaType = getMediaType(response, mediaTypeType, requireValidation); + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); + } + + 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()) { @@ -251,7 +272,8 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean } else { throw HttpUtil.createHttpError("unsupported headers type: " + headersType); } - return validateConstraints(requireValidation, headerMap, headersType, "header binding failed"); + Object convertedHeaderMap = ValueUtils.convert(headerMap, headersType); + return validateConstraints(requireValidation, convertedHeaderMap, headersType, "header binding failed"); } private static boolean hasHttpResponseType(Type targetType) { From 01bf7f3367c084eff254d770c1f57bf76a28e8cb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 07:41:25 +0530 Subject: [PATCH 14/30] Add basic test case matching all the status code responses --- ...sc_binding_with_all_status_codes_tests.bal | 510 ++++++++++++++++++ .../tests/test_service_ports.bal | 2 + 2 files changed, 512 insertions(+) create mode 100644 ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal diff --git a/ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal b/ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal new file mode 100644 index 0000000000..f5e25b4a6b --- /dev/null +++ b/ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal @@ -0,0 +1,510 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/test; + +final http:Client statusCodeBindingClient1 = check new (string `localhost:${statusCodeBindingPort1}`); + +service /api on new http:Listener(statusCodeBindingPort1) { + + resource function get status\-code\-response(int code) returns http:StatusCodeResponse { + return getStatusCodeResponse(code); + } +} + +function getStatusCodeResponse(int code) returns http:StatusCodeResponse { + match code { + // Cannot test these since these status codes expects different behaviors at transport level + // 100 => { + // return {body: {msg: "Continue response"}, mediaType: "application/org+json", headers: {"x-error": "Continue"}}; + // } + // 101 => { + // return {body: {msg: "Switching protocols response"}, mediaType: "application/org+json", headers: {"x-error": "Switching protocols"}}; + // } + 102 => { + return {body: {msg: "Processing response"}, mediaType: "application/org+json", headers: {"x-error": "Processing"}}; + } + 103 => { + return {body: {msg: "Early hints response"}, mediaType: "application/org+json", headers: {"x-error": "Early hints"}}; + } + 200 => { + return {body: {msg: "OK response"}, mediaType: "application/org+json", headers: {"x-error": "OK"}}; + } + 201 => { + return {body: {msg: "Created response"}, mediaType: "application/org+json", headers: {"x-error": "Created"}}; + } + 202 => { + return {body: {msg: "Accepted response"}, mediaType: "application/org+json", headers: {"x-error": "Accepted"}}; + } + 203 => { + return {body: {msg: "Non-authoritative information response"}, mediaType: "application/org+json", headers: {"x-error": "Non-authoritative information"}}; + } + 204 => { + return {headers: {"x-error": "No content"}}; + } + 205 => { + return {body: {msg: "Reset content response"}, mediaType: "application/org+json", headers: {"x-error": "Reset content"}}; + } + 206 => { + return {body: {msg: "Partial content response"}, mediaType: "application/org+json", headers: {"x-error": "Partial content"}}; + } + 207 => { + return {body: {msg: "Multi-status response"}, mediaType: "application/org+json", headers: {"x-error": "Multi-status"}}; + } + 208 => { + return {body: {msg: "Already reported response"}, mediaType: "application/org+json", headers: {"x-error": "Already reported"}}; + } + 226 => { + return {body: {msg: "IM used response"}, mediaType: "application/org+json", headers: {"x-error": "IM used"}}; + } + 300 => { + return {body: {msg: "Multiple choices response"}, mediaType: "application/org+json", headers: {"x-error": "Multiple choices"}}; + } + 301 => { + return {body: {msg: "Moved permanently response"}, mediaType: "application/org+json", headers: {"x-error": "Moved permanently"}}; + } + 302 => { + return {body: {msg: "Found response"}, mediaType: "application/org+json", headers: {"x-error": "Found"}}; + } + 303 => { + return {body: {msg: "See other response"}, mediaType: "application/org+json", headers: {"x-error": "See other"}}; + } + 304 => { + return {body: {msg: "Not modified response"}, mediaType: "application/org+json", headers: {"x-error": "Not modified"}}; + } + 305 => { + return {body: {msg: "Use proxy response"}, mediaType: "application/org+json", headers: {"x-error": "Use proxy"}}; + } + 307 => { + return {body: {msg: "Temporary redirect response"}, mediaType: "application/org+json", headers: {"x-error": "Temporary redirect"}}; + } + 308 => { + return {body: {msg: "Permanent redirect response"}, mediaType: "application/org+json", headers: {"x-error": "Permanent redirect"}}; + } + 400 => { + return {body: {msg: "Bad request error"}, mediaType: "application/org+json", headers: {"x-error": "Bad request"}}; + } + 401 => { + return {body: {msg: "Unauthorized error"}, mediaType: "application/org+json", headers: {"x-error": "Unauthorized"}}; + } + 402 => { + return {body: {msg: "Payment required error"}, mediaType: "application/org+json", headers: {"x-error": "Payment required"}}; + } + 403 => { + return {body: {msg: "Forbidden error"}, mediaType: "application/org+json", headers: {"x-error": "Forbidden"}}; + } + 404 => { + return {body: {msg: "Not found error"}, mediaType: "application/org+json", headers: {"x-error": "Not found"}}; + } + 405 => { + return {body: {msg: "Method not allowed error"}, mediaType: "application/org+json", headers: {"x-error": "Method not allowed"}}; + } + 406 => { + return {body: {msg: "Not acceptable error"}, mediaType: "application/org+json", headers: {"x-error": "Not acceptable"}}; + } + 407 => { + return {body: {msg: "Proxy authentication required error"}, mediaType: "application/org+json", headers: {"x-error": "Proxy authentication required"}}; + } + 408 => { + return {body: {msg: "Request timeout error"}, mediaType: "application/org+json", headers: {"x-error": "Request timeout"}}; + } + 409 => { + return {body: {msg: "Conflict error"}, mediaType: "application/org+json", headers: {"x-error": "Conflict"}}; + } + 410 => { + return {body: {msg: "Gone error"}, mediaType: "application/org+json", headers: {"x-error": "Gone"}}; + } + 411 => { + return {body: {msg: "Length required error"}, mediaType: "application/org+json", headers: {"x-error": "Length required"}}; + } + 412 => { + return {body: {msg: "Precondition failed error"}, mediaType: "application/org+json", headers: {"x-error": "Precondition failed"}}; + } + 413 => { + return {body: {msg: "Payload too large error"}, mediaType: "application/org+json", headers: {"x-error": "Payload too large"}}; + } + 414 => { + return {body: {msg: "URI too long error"}, mediaType: "application/org+json", headers: {"x-error": "URI too long"}}; + } + 415 => { + return {body: {msg: "Unsupported media type error"}, mediaType: "application/org+json", headers: {"x-error": "Unsupported media type"}}; + } + 416 => { + return {body: {msg: "Range not satisfiable error"}, mediaType: "application/org+json", headers: {"x-error": "Range not satisfiable"}}; + } + 417 => { + return {body: {msg: "Expectation failed error"}, mediaType: "application/org+json", headers: {"x-error": "Expectation failed"}}; + } + 421 => { + return {body: {msg: "Misdirected request error"}, mediaType: "application/org+json", headers: {"x-error": "Misdirected request"}}; + } + 422 => { + return {body: {msg: "Unprocessable entity error"}, mediaType: "application/org+json", headers: {"x-error": "Unprocessable entity"}}; + } + 423 => { + return {body: {msg: "Locked error"}, mediaType: "application/org+json", headers: {"x-error": "Locked"}}; + } + 424 => { + return {body: {msg: "Failed dependency error"}, mediaType: "application/org+json", headers: {"x-error": "Failed dependency"}}; + } + 425 => { + return {body: {msg: "Too early error"}, mediaType: "application/org+json", headers: {"x-error": "Too early"}}; + } + 426 => { + return {body: {msg: "Upgrade required error"}, mediaType: "application/org+json", headers: {"x-error": "Upgrade required"}}; + } + 428 => { + return {body: {msg: "Precondition required error"}, mediaType: "application/org+json", headers: {"x-error": "Precondition required"}}; + } + 429 => { + return {body: {msg: "Too many requests error"}, mediaType: "application/org+json", headers: {"x-error": "Too many requests"}}; + } + 431 => { + return {body: {msg: "Request header fields too large error"}, mediaType: "application/org+json", headers: {"x-error": "Request header fields too large"}}; + } + 451 => { + return {body: {msg: "Unavailable for legal reasons error"}, mediaType: "application/org+json", headers: {"x-error": "Unavailable for legal reasons"}}; + } + 500 => { + return {body: {msg: "Internal server error"}, mediaType: "application/org+json", headers: {"x-error": "Internal server error"}}; + } + 501 => { + return {body: {msg: "Not implemented error"}, mediaType: "application/org+json", headers: {"x-error": "Not implemented"}}; + } + 502 => { + return {body: {msg: "Bad gateway error"}, mediaType: "application/org+json", headers: {"x-error": "Bad gateway"}}; + } + 503 => { + return {body: {msg: "Service unavailable error"}, mediaType: "application/org+json", headers: {"x-error": "Service unavailable"}}; + } + 504 => { + return {body: {msg: "Gateway timeout error"}, mediaType: "application/org+json", headers: {"x-error": "Gateway timeout"}}; + } + 505 => { + return {body: {msg: "HTTP version not supported error"}, mediaType: "application/org+json", headers: {"x-error": "HTTP version not supported"}}; + } + 506 => { + return {body: {msg: "Variant also negotiates error"}, mediaType: "application/org+json", headers: {"x-error": "Variant also negotiates"}}; + } + 507 => { + return {body: {msg: "Insufficient storage error"}, mediaType: "application/org+json", headers: {"x-error": "Insufficient storage"}}; + } + 508 => { + return {body: {msg: "Loop detected error"}, mediaType: "application/org+json", headers: {"x-error": "Loop detected"}}; + } + 510 => { + return {body: {msg: "Not extended error"}, mediaType: "application/org+json", headers: {"x-error": "Not extended"}}; + } + 511 => { + return {body: {msg: "Network authentication required error"}, mediaType: "application/org+json", headers: {"x-error": "Network authentication required"}}; + } + _ => { + return {body: {msg: "Bad request with unknown status code"}, mediaType: "application/org+json", headers: {"x-error": "Unknown status code"}}; + } + } +} + +@test:Config {} +function testSCResBindingWith1XXStatusCodes() returns error? { + http:Processing processingResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 102); + test:assertTrue(processingResponse is http:Processing, "Response type mismatched"); + testStatusCodeResponse(processingResponse, 102, "Processing", "Processing response"); + + http:EarlyHints earlyHintsResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 103); + test:assertTrue(earlyHintsResponse is http:EarlyHints, "Response type mismatched"); + testStatusCodeResponse(earlyHintsResponse, 103, "Early hints", "Early hints response"); +} + +@test:Config {} +function testSCResBindingWith2XXStatusCodes() returns error? { + http:Ok okResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 200); + test:assertTrue(okResponse is http:Ok, "Response type mismatched"); + testStatusCodeResponse(okResponse, 200, "OK", "OK response"); + + http:Created createdResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 201); + test:assertTrue(createdResponse is http:Created, "Response type mismatched"); + testStatusCodeResponse(createdResponse, 201, "Created", "Created response"); + + http:Accepted acceptedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 202); + test:assertTrue(acceptedResponse is http:Accepted, "Response type mismatched"); + testStatusCodeResponse(acceptedResponse, 202, "Accepted", "Accepted response"); + + http:NonAuthoritativeInformation nonAuthoritativeInformationResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 203); + test:assertTrue(nonAuthoritativeInformationResponse is http:NonAuthoritativeInformation, "Response type mismatched"); + testStatusCodeResponse(nonAuthoritativeInformationResponse, 203, "Non-authoritative information", "Non-authoritative information response"); + + http:NoContent noContentResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 204); + test:assertTrue(noContentResponse is http:NoContent, "Response type mismatched"); + testStatusCodeResponse(noContentResponse, 204, "No content"); + + http:ResetContent resetContentResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 205); + test:assertTrue(resetContentResponse is http:ResetContent, "Response type mismatched"); + testStatusCodeResponse(resetContentResponse, 205, "Reset content", "Reset content response"); + + http:PartialContent partialContentResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 206); + test:assertTrue(partialContentResponse is http:PartialContent, "Response type mismatched"); + testStatusCodeResponse(partialContentResponse, 206, "Partial content", "Partial content response"); + + http:MultiStatus multiStatusResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 207); + test:assertTrue(multiStatusResponse is http:MultiStatus, "Response type mismatched"); + testStatusCodeResponse(multiStatusResponse, 207, "Multi-status", "Multi-status response"); + + http:AlreadyReported alreadyReportedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 208); + test:assertTrue(alreadyReportedResponse is http:AlreadyReported, "Response type mismatched"); + testStatusCodeResponse(alreadyReportedResponse, 208, "Already reported", "Already reported response"); + + http:IMUsed imUsedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 226); + test:assertTrue(imUsedResponse is http:IMUsed, "Response type mismatched"); + testStatusCodeResponse(imUsedResponse, 226, "IM used", "IM used response"); +} + +@test:Config {} +function testSCResBindingWith3XXStatusCodes() returns error? { + http:MultipleChoices multipleChoicesResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 300); + test:assertTrue(multipleChoicesResponse is http:MultipleChoices, "Response type mismatched"); + testStatusCodeResponse(multipleChoicesResponse, 300, "Multiple choices", "Multiple choices response"); + + http:MovedPermanently movedPermanentlyResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 301); + test:assertTrue(movedPermanentlyResponse is http:MovedPermanently, "Response type mismatched"); + testStatusCodeResponse(movedPermanentlyResponse, 301, "Moved permanently", "Moved permanently response"); + + http:Found foundResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 302); + test:assertTrue(foundResponse is http:Found, "Response type mismatched"); + testStatusCodeResponse(foundResponse, 302, "Found", "Found response"); + + http:SeeOther seeOtherResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 303); + test:assertTrue(seeOtherResponse is http:SeeOther, "Response type mismatched"); + testStatusCodeResponse(seeOtherResponse, 303, "See other", "See other response"); + + http:NotModified notModifiedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 304); + test:assertTrue(notModifiedResponse is http:NotModified, "Response type mismatched"); + testStatusCodeResponse(notModifiedResponse, 304, "Not modified", "Not modified response"); + + http:UseProxy useProxyResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 305); + test:assertTrue(useProxyResponse is http:UseProxy, "Response type mismatched"); + testStatusCodeResponse(useProxyResponse, 305, "Use proxy", "Use proxy response"); + + http:TemporaryRedirect temporaryRedirectResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 307); + test:assertTrue(temporaryRedirectResponse is http:TemporaryRedirect, "Response type mismatched"); + testStatusCodeResponse(temporaryRedirectResponse, 307, "Temporary redirect", "Temporary redirect response"); + + http:PermanentRedirect permanentRedirectResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 308); + test:assertTrue(permanentRedirectResponse is http:PermanentRedirect, "Response type mismatched"); + testStatusCodeResponse(permanentRedirectResponse, 308, "Permanent redirect", "Permanent redirect response"); +} + +@test:Config {} +function testSCResBindingWith4XXStatusCodes() returns error? { + http:BadRequest badRequestResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 400); + test:assertTrue(badRequestResponse is http:BadRequest, "Response type mismatched"); + testStatusCodeResponse(badRequestResponse, 400, "Bad request", "Bad request error"); + + http:Unauthorized unauthorizedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 401); + test:assertTrue(unauthorizedResponse is http:Unauthorized, "Response type mismatched"); + testStatusCodeResponse(unauthorizedResponse, 401, "Unauthorized", "Unauthorized error"); + + http:PaymentRequired paymentRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 402); + test:assertTrue(paymentRequiredResponse is http:PaymentRequired, "Response type mismatched"); + testStatusCodeResponse(paymentRequiredResponse, 402, "Payment required", "Payment required error"); + + http:Forbidden forbiddenResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 403); + test:assertTrue(forbiddenResponse is http:Forbidden, "Response type mismatched"); + testStatusCodeResponse(forbiddenResponse, 403, "Forbidden", "Forbidden error"); + + http:NotFound notFoundResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 404); + test:assertTrue(notFoundResponse is http:NotFound, "Response type mismatched"); + testStatusCodeResponse(notFoundResponse, 404, "Not found", "Not found error"); + + http:MethodNotAllowed methodNotAllowedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 405); + test:assertTrue(methodNotAllowedResponse is http:MethodNotAllowed, "Response type mismatched"); + testStatusCodeResponse(methodNotAllowedResponse, 405, "Method not allowed", "Method not allowed error"); + + http:NotAcceptable notAcceptableResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 406); + test:assertTrue(notAcceptableResponse is http:NotAcceptable, "Response type mismatched"); + testStatusCodeResponse(notAcceptableResponse, 406, "Not acceptable", "Not acceptable error"); + + http:ProxyAuthenticationRequired proxyAuthenticationRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 407); + test:assertTrue(proxyAuthenticationRequiredResponse is http:ProxyAuthenticationRequired, "Response type mismatched"); + testStatusCodeResponse(proxyAuthenticationRequiredResponse, 407, "Proxy authentication required", "Proxy authentication required error"); + + http:RequestTimeout requestTimeoutResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 408); + test:assertTrue(requestTimeoutResponse is http:RequestTimeout, "Response type mismatched"); + testStatusCodeResponse(requestTimeoutResponse, 408, "Request timeout", "Request timeout error"); + + http:Conflict conflictResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 409); + test:assertTrue(conflictResponse is http:Conflict, "Response type mismatched"); + testStatusCodeResponse(conflictResponse, 409, "Conflict", "Conflict error"); + + http:Gone goneResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 410); + test:assertTrue(goneResponse is http:Gone, "Response type mismatched"); + testStatusCodeResponse(goneResponse, 410, "Gone", "Gone error"); + + http:LengthRequired lengthRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 411); + test:assertTrue(lengthRequiredResponse is http:LengthRequired, "Response type mismatched"); + testStatusCodeResponse(lengthRequiredResponse, 411, "Length required", "Length required error"); + + http:PreconditionFailed preconditionFailedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 412); + test:assertTrue(preconditionFailedResponse is http:PreconditionFailed, "Response type mismatched"); + testStatusCodeResponse(preconditionFailedResponse, 412, "Precondition failed", "Precondition failed error"); + + http:PayloadTooLarge payloadTooLargeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 413); + test:assertTrue(payloadTooLargeResponse is http:PayloadTooLarge, "Response type mismatched"); + testStatusCodeResponse(payloadTooLargeResponse, 413, "Payload too large", "Payload too large error"); + + http:UriTooLong uriTooLongResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 414); + test:assertTrue(uriTooLongResponse is http:UriTooLong, "Response type mismatched"); + testStatusCodeResponse(uriTooLongResponse, 414, "URI too long", "URI too long error"); + + http:UnsupportedMediaType unsupportedMediaTypeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 415); + test:assertTrue(unsupportedMediaTypeResponse is http:UnsupportedMediaType, "Response type mismatched"); + testStatusCodeResponse(unsupportedMediaTypeResponse, 415, "Unsupported media type", "Unsupported media type error"); + + http:RangeNotSatisfiable rangeNotSatisfiableResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 416); + test:assertTrue(rangeNotSatisfiableResponse is http:RangeNotSatisfiable, "Response type mismatched"); + testStatusCodeResponse(rangeNotSatisfiableResponse, 416, "Range not satisfiable", "Range not satisfiable error"); + + http:ExpectationFailed expectationFailedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 417); + test:assertTrue(expectationFailedResponse is http:ExpectationFailed, "Response type mismatched"); + testStatusCodeResponse(expectationFailedResponse, 417, "Expectation failed", "Expectation failed error"); + + http:MisdirectedRequest misdirectedRequestResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 421); + test:assertTrue(misdirectedRequestResponse is http:MisdirectedRequest, "Response type mismatched"); + testStatusCodeResponse(misdirectedRequestResponse, 421, "Misdirected request", "Misdirected request error"); + + http:UnprocessableEntity unprocessableEntityResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 422); + test:assertTrue(unprocessableEntityResponse is http:UnprocessableEntity, "Response type mismatched"); + testStatusCodeResponse(unprocessableEntityResponse, 422, "Unprocessable entity", "Unprocessable entity error"); + + http:Locked lockedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 423); + test:assertTrue(lockedResponse is http:Locked, "Response type mismatched"); + testStatusCodeResponse(lockedResponse, 423, "Locked", "Locked error"); + + http:FailedDependency failedDependencyResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 424); + test:assertTrue(failedDependencyResponse is http:FailedDependency, "Response type mismatched"); + testStatusCodeResponse(failedDependencyResponse, 424, "Failed dependency", "Failed dependency error"); + + http:TooEarly tooEarlyResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 425); + test:assertTrue(tooEarlyResponse is http:TooEarly, "Response type mismatched"); + testStatusCodeResponse(tooEarlyResponse, 425, "Too early", "Too early error"); + + http:UpgradeRequired upgradeRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 426); + test:assertTrue(upgradeRequiredResponse is http:UpgradeRequired, "Response type mismatched"); + testStatusCodeResponse(upgradeRequiredResponse, 426, "Upgrade required", "Upgrade required error"); + + http:PreconditionRequired preconditionRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 428); + test:assertTrue(preconditionRequiredResponse is http:PreconditionRequired, "Response type mismatched"); + testStatusCodeResponse(preconditionRequiredResponse, 428, "Precondition required", "Precondition required error"); + + http:TooManyRequests tooManyRequestsResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 429); + test:assertTrue(tooManyRequestsResponse is http:TooManyRequests, "Response type mismatched"); + testStatusCodeResponse(tooManyRequestsResponse, 429, "Too many requests", "Too many requests error"); + + http:RequestHeaderFieldsTooLarge requestHeaderFieldsTooLargeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 431); + test:assertTrue(requestHeaderFieldsTooLargeResponse is http:RequestHeaderFieldsTooLarge, "Response type mismatched"); + testStatusCodeResponse(requestHeaderFieldsTooLargeResponse, 431, "Request header fields too large", "Request header fields too large error"); + + http:UnavailableDueToLegalReasons unavailableDueToLegalReasonsResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 451); + test:assertTrue(unavailableDueToLegalReasonsResponse is http:UnavailableDueToLegalReasons, "Response type mismatched"); + testStatusCodeResponse(unavailableDueToLegalReasonsResponse, 451, "Unavailable for legal reasons", "Unavailable for legal reasons error"); +} + +@test:Config {} +function testSCResBindingWith5XXStatusCodes() returns error? { + http:InternalServerError internalServerErrorResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 500); + test:assertTrue(internalServerErrorResponse is http:InternalServerError, "Response type mismatched"); + testStatusCodeResponse(internalServerErrorResponse, 500, "Internal server error", "Internal server error"); + + http:NotImplemented notImplementedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 501); + test:assertTrue(notImplementedResponse is http:NotImplemented, "Response type mismatched"); + testStatusCodeResponse(notImplementedResponse, 501, "Not implemented", "Not implemented error"); + + http:BadGateway badGatewayResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 502); + test:assertTrue(badGatewayResponse is http:BadGateway, "Response type mismatched"); + testStatusCodeResponse(badGatewayResponse, 502, "Bad gateway", "Bad gateway error"); + + http:ServiceUnavailable serviceUnavailableResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 503); + test:assertTrue(serviceUnavailableResponse is http:ServiceUnavailable, "Response type mismatched"); + testStatusCodeResponse(serviceUnavailableResponse, 503, "Service unavailable", "Service unavailable error"); + + http:GatewayTimeout gatewayTimeoutResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 504); + test:assertTrue(gatewayTimeoutResponse is http:GatewayTimeout, "Response type mismatched"); + testStatusCodeResponse(gatewayTimeoutResponse, 504, "Gateway timeout", "Gateway timeout error"); + + http:HttpVersionNotSupported httpVersionNotSupportedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 505); + test:assertTrue(httpVersionNotSupportedResponse is http:HttpVersionNotSupported, "Response type mismatched"); + testStatusCodeResponse(httpVersionNotSupportedResponse, 505, "HTTP version not supported", "HTTP version not supported error"); + + http:VariantAlsoNegotiates variantAlsoNegotiatesResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 506); + test:assertTrue(variantAlsoNegotiatesResponse is http:VariantAlsoNegotiates, "Response type mismatched"); + testStatusCodeResponse(variantAlsoNegotiatesResponse, 506, "Variant also negotiates", "Variant also negotiates error"); + + http:InsufficientStorage insufficientStorageResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 507); + test:assertTrue(insufficientStorageResponse is http:InsufficientStorage, "Response type mismatched"); + testStatusCodeResponse(insufficientStorageResponse, 507, "Insufficient storage", "Insufficient storage error"); + + http:LoopDetected loopDetectedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 508); + test:assertTrue(loopDetectedResponse is http:LoopDetected, "Response type mismatched"); + testStatusCodeResponse(loopDetectedResponse, 508, "Loop detected", "Loop detected error"); + + http:NotExtended notExtendedResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 510); + test:assertTrue(notExtendedResponse is http:NotExtended, "Response type mismatched"); + testStatusCodeResponse(notExtendedResponse, 510, "Not extended", "Not extended error"); + + http:NetworkAuthenticationRequired networkAuthenticationRequiredResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 511); + test:assertTrue(networkAuthenticationRequiredResponse is http:NetworkAuthenticationRequired, "Response type mismatched"); + testStatusCodeResponse(networkAuthenticationRequiredResponse, 511, "Network authentication required", "Network authentication required error"); + + http:BadRequest badRequestWithUnknownStatusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 999); + test:assertTrue(badRequestWithUnknownStatusCodeResponse is http:BadRequest, "Response type mismatched"); + testStatusCodeResponse(badRequestWithUnknownStatusCodeResponse, 400, "Unknown status code", "Bad request with unknown status code"); +} + +@test:Config {} +function testSCResBindingWithCommonType() returns error? { + http:StatusCodeResponse statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 103); + test:assertTrue(statusCodeResponse is http:EarlyHints, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 103, "Early hints", "Early hints response"); + + statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 201); + test:assertTrue(statusCodeResponse is http:Created, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 201, "Created", "Created response"); + + statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 204); + test:assertTrue(statusCodeResponse is http:NoContent, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 204, "No content"); + + statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 304); + test:assertTrue(statusCodeResponse is http:NotModified, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 304, "Not modified", "Not modified response"); + + statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 405); + test:assertTrue(statusCodeResponse is http:MethodNotAllowed, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 405, "Method not allowed", "Method not allowed error"); + + statusCodeResponse = check statusCodeBindingClient1->/api/status\-code\-response(code = 504); + test:assertTrue(statusCodeResponse is http:GatewayTimeout, "Response type mismatched"); + testStatusCodeResponse(statusCodeResponse, 504, "Gateway timeout", "Gateway timeout error"); +} + +function testStatusCodeResponse(http:StatusCodeResponse statusCodeResponse, int statusCode, string header, string? body = ()) { + test:assertEquals(statusCodeResponse.status.code, statusCode, "Status code mismatched"); + test:assertEquals(statusCodeResponse.headers["x-error"], header, "Header mismatched"); + if body is string { + test:assertEquals(statusCodeResponse?.body, {msg: body}, "Response body mismatched"); + test:assertEquals(statusCodeResponse?.mediaType, "application/org+json", "Media type mismatched"); + } +} diff --git a/ballerina-tests/http-client-tests/tests/test_service_ports.bal b/ballerina-tests/http-client-tests/tests/test_service_ports.bal index 706826a252..2fa44e033e 100644 --- a/ballerina-tests/http-client-tests/tests/test_service_ports.bal +++ b/ballerina-tests/http-client-tests/tests/test_service_ports.bal @@ -37,3 +37,5 @@ const int http2ClientHostHeaderTestPort = 9605; const int httpClientHostHeaderTestPort = 9606; const int passthroughHostTestPort1 = 9607; const int passthroughHostTestPort2 = 9608; + +const int statusCodeBindingPort1 = 9609; From bdd8d747743168015647b53e143c980bbba39fbf Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 09:12:16 +0530 Subject: [PATCH 15/30] Change the error type for success response with unsupported target type --- .../java/io/ballerina/stdlib/http/api/HttpConstants.java | 1 + .../http/api/nativeimpl/ExternResponseProcessor.java | 9 ++++++--- 2 files changed, 7 insertions(+), 3 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 97c7895ca9..84af585f01 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 @@ -295,6 +295,7 @@ public final class HttpConstants { public static final String STATUS_CODE_RESPONSE_BODY_FIELD = "body"; public static final String STATUS_CODE_RESPONSE_STATUS_FIELD = "status"; public static final String STATUS_CODE_RESPONSE_STATUS_CODE_FIELD = "code"; + public static final String STATUS_CODE = "statusCode"; public static final String STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD = "mediaType"; public static final String STATUS_CODE_RESPONSE_HEADERS_FIELD = "headers"; 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 101eb8aff7..34d4a82606 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 @@ -5,6 +5,7 @@ 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; @@ -42,6 +43,7 @@ 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; import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_BODY_FIELD; import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_HEADERS_FIELD; import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD; @@ -133,7 +135,7 @@ public static Object processResponse(Environment env, BObject response, BTypedes private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, Runtime runtime) { - long responseStatusCode = response.getIntValue(StringUtils.fromString("statusCode")); + long responseStatusCode = response.getIntValue(StringUtils.fromString(STATUS_CODE)); Optional statusCodeResponseType = getStatusCodeResponseType(targetType, Long.toString(responseStatusCode)); if (statusCodeResponseType.isPresent() && @@ -155,7 +157,8 @@ private static Object generatePayload(BObject response, Type targetType, boolean if (hasHttpResponseType(targetType)) { return response; } - return e; + return HttpUtil.createHttpError("incompatible " + targetType + " found for response with " + + response.getIntValue(StringUtils.fromString(STATUS_CODE)), PAYLOAD_BINDING_CLIENT_ERROR, e); } } @@ -213,7 +216,7 @@ private static void populateStatusCodeObject(String statusCodeObjName, BMap anydataTypes = extractAnydataTypes(targetType, new ArrayList<>()); if (anydataTypes.isEmpty()) { - throw HttpUtil.createHttpError("unsupported target type: " + targetType); + throw ErrorCreator.createError(StringUtils.fromString("no 'anydata' type found in the target type")); } else if (anydataTypes.size() == 1) { return anydataTypes.get(0); } else { From 93a148f17b9e1c9ecb2f14de26b5bf4379856deb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 09:12:33 +0530 Subject: [PATCH 16/30] Add status code response binding general tests --- .../tests/sc_res_binding_tests.bal | 267 ++++++++++++++++++ ...s_binding_with_all_status_codes_tests.bal} | 0 .../tests/test_service_ports.bal | 1 + 3 files changed, 268 insertions(+) create mode 100644 ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal rename ballerina-tests/http-client-tests/tests/{sc_binding_with_all_status_codes_tests.bal => sc_res_binding_with_all_status_codes_tests.bal} (100%) 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 new file mode 100644 index 0000000000..b69e302f8a --- /dev/null +++ b/ballerina-tests/http-client-tests/tests/sc_res_binding_tests.bal @@ -0,0 +1,267 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/test; + +type Album record {| + readonly string id; + string name; + string artist; + string genre; +|}; + +table key(id) albums = table [ + {id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"}, + {id: "2", name: "Back in Black", artist: "AC/DC", genre: "Hard Rock"}, + {id: "3", name: "The Wall", artist: "Pink Floyd", genre: "Progressive Rock"} +]; + +type ErrorMessage record {| + string albumId; + string message; +|}; + +type Headers record {| + string user\-id; + int req\-id; +|}; + +type AlbumNotFound record {| + *http:NotFound; + ErrorMessage body; + Headers headers; +|}; + +type AlbumFound record {| + *http:Ok; + Album body; + Headers headers; +|}; + +service /api on new http:Listener(statusCodeBindingPort2) { + + resource function get albums/[string id]() returns AlbumFound|AlbumNotFound { + if albums.hasKey(id) { + return { + body: albums.get(id), + headers: {user\-id: "user-1", req\-id: 1} + }; + } + return { + body: {albumId: id, message: "Album not found"}, + headers: {user\-id: "user-1", req\-id: 1} + }; + } +} + +final http:Client albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`); + +public function main() returns error? { + Album _ = check albumClient->/albums/'1; + + AlbumFound _ = check albumClient->/albums/'1; + + Album|AlbumNotFound _ = check albumClient->/albums/'1; + + AlbumFound|AlbumNotFound _ = check albumClient->/albums/'1; + + Album|http:Response _ = check albumClient->/albums/'1; +} + +@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->/albums/'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->/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->/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"); + test:assertEquals(res4.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res4.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res4.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } + + Album|AlbumFound|AlbumNotFound res5 = check albumClient->/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->/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 status_code_binding_test:AlbumNotFound found for response with 200", + "Invalid error message"); + error? cause = res8.cause(); + if cause is error { + test:assertEquals(cause.message(), "no 'anydata' type found in the target type", "Invalid cause error message"); + } + } else { + test:assertFail("Invalid response type"); + } +} + +@test:Config {} +function testGetFailureStatusCodeResponse() returns error? { + AlbumNotFound albumNotFound = check albumClient->/albums/'4; + ErrorMessage expectedErrorMessage = {albumId: "4", message: "Album not found"}; + test:assertEquals(albumNotFound.body, expectedErrorMessage, "Invalid error message"); + test:assertEquals(albumNotFound.headers.user\-id, "user-1", "Invalid user-id header"); + 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->/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->/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->/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->/albums/'4; + if res6 is error { + test:assertTrue(res6 is http:ClientRequestError); + test:assertEquals(res6.message(), "Not Found", "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 response type"); + } +} diff --git a/ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal b/ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal similarity index 100% rename from ballerina-tests/http-client-tests/tests/sc_binding_with_all_status_codes_tests.bal rename to ballerina-tests/http-client-tests/tests/sc_res_binding_with_all_status_codes_tests.bal diff --git a/ballerina-tests/http-client-tests/tests/test_service_ports.bal b/ballerina-tests/http-client-tests/tests/test_service_ports.bal index 2fa44e033e..4086c78b29 100644 --- a/ballerina-tests/http-client-tests/tests/test_service_ports.bal +++ b/ballerina-tests/http-client-tests/tests/test_service_ports.bal @@ -39,3 +39,4 @@ const int passthroughHostTestPort1 = 9607; const int passthroughHostTestPort2 = 9608; const int statusCodeBindingPort1 = 9609; +const int statusCodeBindingPort2 = 9610; \ No newline at end of file From 763020d8f2d1bd52b249198d1c40c2cb8e1123ed Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 09:13:36 +0530 Subject: [PATCH 17/30] Fix test failure --- .../http-client-tests/tests/sc_res_binding_tests.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b69e302f8a..ed4b4d00b2 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 @@ -167,7 +167,7 @@ function testGetSuccessStatusCodeResponse() returns error? { AlbumNotFound|error res8 = albumClient->/albums/'1; if res8 is error { test:assertTrue(res8 is http:PayloadBindingError); - test:assertEquals(res8.message(), "incompatible status_code_binding_test:AlbumNotFound found for response with 200", + test:assertEquals(res8.message(), "incompatible http_client_tests:AlbumNotFound found for response with 200", "Invalid error message"); error? cause = res8.cause(); if cause is error { From 5306c960d3683b2dced7917096482348cf165ca4 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 09:29:07 +0530 Subject: [PATCH 18/30] Add remote function tests --- .../tests/sc_res_binding_tests.bal | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 ed4b4d00b2..c7c56e800b 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 @@ -88,7 +88,7 @@ function testGetSuccessStatusCodeResponse() returns error? { Album expectedAlbum = albums.get("1"); test:assertEquals(album, expectedAlbum, "Invalid album returned"); - AlbumFound albumFound = check albumClient->/albums/'1; + AlbumFound albumFound = check albumClient->get("/albums/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"); @@ -100,7 +100,7 @@ function testGetSuccessStatusCodeResponse() returns error? { album = check payload.fromJsonWithType(); test:assertEquals(album, expectedAlbum, "Invalid album returned"); - Album|AlbumFound res1 = check albumClient->/albums/'1; + 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"); @@ -120,7 +120,7 @@ function testGetSuccessStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - Album|AlbumNotFound res3 = check albumClient->/albums/'1; + Album|AlbumNotFound res3 = check albumClient->get("/albums/1"); if res3 is Album { test:assertEquals(res3, expectedAlbum, "Invalid album returned"); } else { @@ -137,7 +137,7 @@ function testGetSuccessStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - Album|AlbumFound|AlbumNotFound res5 = check albumClient->/albums/'1; + 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"); @@ -154,7 +154,7 @@ function testGetSuccessStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - AlbumNotFound|http:Response res7 = check albumClient->/albums/'1; + 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(); @@ -167,7 +167,7 @@ function testGetSuccessStatusCodeResponse() returns error? { 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:assertEquals(res8.message(), "incompatible status_code_binding_test:AlbumNotFound found for response with 200", "Invalid error message"); error? cause = res8.cause(); if cause is error { @@ -187,7 +187,7 @@ 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->/albums/'4; + 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(); @@ -203,7 +203,7 @@ function testGetFailureStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - AlbumNotFound|http:Response res2 = check albumClient->/albums/'4; + 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"); @@ -223,7 +223,7 @@ function testGetFailureStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - http:Response|AlbumFound res4 = check albumClient->/albums/'4; + 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(); @@ -249,7 +249,7 @@ function testGetFailureStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } - AlbumFound|error res6 = albumClient->/albums/'4; + 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"); From bce164f18e1e33b548e2db2aa20f294e437a2202 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 10:29:40 +0530 Subject: [PATCH 19/30] Add suggestions from code review --- .../tests/sc_res_binding_tests.bal | 2 +- .../tests/test_service_ports.bal | 2 +- ballerina/http_client_endpoint.bal | 48 ++--- ballerina/resiliency_failover_client.bal | 48 ++--- ballerina/resiliency_load_balance_client.bal | 40 +--- .../nativeimpl/ExternResponseProcessor.java | 180 +++++++++--------- 6 files changed, 137 insertions(+), 183 deletions(-) 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 c7c56e800b..87ad3727f4 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 @@ -167,7 +167,7 @@ function testGetSuccessStatusCodeResponse() returns error? { AlbumNotFound|error res8 = albumClient->/albums/'1; if res8 is error { test:assertTrue(res8 is http:PayloadBindingError); - test:assertEquals(res8.message(), "incompatible status_code_binding_test:AlbumNotFound found for response with 200", + test:assertEquals(res8.message(), "incompatible http_client_tests:AlbumNotFound found for response with 200", "Invalid error message"); error? cause = res8.cause(); if cause is error { diff --git a/ballerina-tests/http-client-tests/tests/test_service_ports.bal b/ballerina-tests/http-client-tests/tests/test_service_ports.bal index 4086c78b29..e2d261c743 100644 --- a/ballerina-tests/http-client-tests/tests/test_service_ports.bal +++ b/ballerina-tests/http-client-tests/tests/test_service_ports.bal @@ -39,4 +39,4 @@ const int passthroughHostTestPort1 = 9607; const int passthroughHostTestPort2 = 9608; const int statusCodeBindingPort1 = 9609; -const int statusCodeBindingPort2 = 9610; \ No newline at end of file +const int statusCodeBindingPort2 = 9610; diff --git a/ballerina/http_client_endpoint.bal b/ballerina/http_client_endpoint.bal index b69ebec5b3..8edb594675 100644 --- a/ballerina/http_client_endpoint.bal +++ b/ballerina/http_client_endpoint.bal @@ -95,10 +95,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_POST, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP PUT requests to HTTP endpoints. @@ -140,10 +137,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PUT, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP PATCH requests to HTTP endpoints. @@ -185,10 +179,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_PATCH, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP DELETE requests to HTTP endpoints. @@ -230,10 +221,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_DELETE, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP HEAD requests to HTTP endpoints. @@ -295,10 +283,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_GET, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The client resource function to send HTTP OPTIONS requests to HTTP endpoints. @@ -334,10 +319,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, HTTP_OPTIONS, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # Invokes an HTTP call with the specified HTTP verb. @@ -365,10 +347,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, httpVerb, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # The `Client.forward()` function can be used to invoke an HTTP call with inbound request's HTTP verb @@ -389,10 +368,7 @@ public client isolated class Client { if observabilityEnabled && response is Response { addObservabilityInformation(path, request.method, response.statusCode, self.url); } - if response is ClientError { - return response; - } - return externProcessResponse(response, targetType, self.requireValidation); + return processResponse(response, targetType, self.requireValidation); } # Submits an HTTP request to a service with the specified HTTP verb. @@ -701,6 +677,14 @@ isolated function createResponseError(int statusCode, string reasonPhrase, map STATUS_CODE_OBJS; + private static final String HEADER_BINDING_FAILED = "header binding failed"; + 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 PERFORM_DATA_BINDING = "performDataBinding"; + private static final String GET_APPLICATION_RESPONSE_ERROR = "getApplicationResponseError"; + + + private static final Map STATUS_CODE_OBJS = new HashMap<>(); static { - Map statusCodeObjectsMap = new HashMap<>(); - statusCodeObjectsMap.put("100", "StatusContinue"); - statusCodeObjectsMap.put("101", "StatusSwitchingProtocols"); - statusCodeObjectsMap.put("102", "StatusProcessing"); - statusCodeObjectsMap.put("103", "StatusEarlyHints"); - statusCodeObjectsMap.put("200", "StatusOK"); - statusCodeObjectsMap.put("201", "StatusCreated"); - statusCodeObjectsMap.put("202", "StatusAccepted"); - statusCodeObjectsMap.put("203", "StatusNonAuthoritativeInformation"); - statusCodeObjectsMap.put("204", "StatusNoContent"); - statusCodeObjectsMap.put("205", "StatusResetContent"); - statusCodeObjectsMap.put("206", "StatusPartialContent"); - statusCodeObjectsMap.put("207", "StatusMultiStatus"); - statusCodeObjectsMap.put("208", "StatusAlreadyReported"); - statusCodeObjectsMap.put("226", "StatusIMUsed"); - statusCodeObjectsMap.put("300", "StatusMultipleChoices"); - statusCodeObjectsMap.put("301", "StatusMovedPermanently"); - statusCodeObjectsMap.put("302", "StatusFound"); - statusCodeObjectsMap.put("303", "StatusSeeOther"); - statusCodeObjectsMap.put("304", "StatusNotModified"); - statusCodeObjectsMap.put("305", "StatusUseProxy"); - statusCodeObjectsMap.put("307", "StatusTemporaryRedirect"); - statusCodeObjectsMap.put("308", "StatusPermanentRedirect"); - statusCodeObjectsMap.put("400", "StatusBadRequest"); - statusCodeObjectsMap.put("401", "StatusUnauthorized"); - statusCodeObjectsMap.put("402", "StatusPaymentRequired"); - statusCodeObjectsMap.put("403", "StatusForbidden"); - statusCodeObjectsMap.put("404", "StatusNotFound"); - statusCodeObjectsMap.put("405", "StatusMethodNotAllowed"); - statusCodeObjectsMap.put("406", "StatusNotAcceptable"); - statusCodeObjectsMap.put("407", "StatusProxyAuthenticationRequired"); - statusCodeObjectsMap.put("408", "StatusRequestTimeout"); - statusCodeObjectsMap.put("409", "StatusConflict"); - statusCodeObjectsMap.put("410", "StatusGone"); - statusCodeObjectsMap.put("411", "StatusLengthRequired"); - statusCodeObjectsMap.put("412", "StatusPreconditionFailed"); - statusCodeObjectsMap.put("413", "StatusPayloadTooLarge"); - statusCodeObjectsMap.put("414", "StatusUriTooLong"); - statusCodeObjectsMap.put("415", "StatusUnsupportedMediaType"); - statusCodeObjectsMap.put("416", "StatusRangeNotSatisfiable"); - statusCodeObjectsMap.put("417", "StatusExpectationFailed"); - statusCodeObjectsMap.put("421", "StatusMisdirectedRequest"); - statusCodeObjectsMap.put("422", "StatusUnprocessableEntity"); - statusCodeObjectsMap.put("423", "StatusLocked"); - statusCodeObjectsMap.put("424", "StatusFailedDependency"); - statusCodeObjectsMap.put("425", "StatusTooEarly"); - statusCodeObjectsMap.put("426", "StatusUpgradeRequired"); - statusCodeObjectsMap.put("428", "StatusPreconditionRequired"); - statusCodeObjectsMap.put("429", "StatusTooManyRequests"); - statusCodeObjectsMap.put("431", "StatusRequestHeaderFieldsTooLarge"); - statusCodeObjectsMap.put("451", "StatusUnavailableDueToLegalReasons"); - statusCodeObjectsMap.put("500", "StatusInternalServerError"); - statusCodeObjectsMap.put("501", "StatusNotImplemented"); - statusCodeObjectsMap.put("502", "StatusBadGateway"); - statusCodeObjectsMap.put("503", "StatusServiceUnavailable"); - statusCodeObjectsMap.put("504", "StatusGatewayTimeout"); - statusCodeObjectsMap.put("505", "StatusHttpVersionNotSupported"); - statusCodeObjectsMap.put("506", "StatusVariantAlsoNegotiates"); - statusCodeObjectsMap.put("507", "StatusInsufficientStorage"); - statusCodeObjectsMap.put("508", "StatusLoopDetected"); - statusCodeObjectsMap.put("510", "StatusNotExtended"); - statusCodeObjectsMap.put("511", "StatusNetworkAuthenticationRequired"); - STATUS_CODE_OBJS = statusCodeObjectsMap; + STATUS_CODE_OBJS.put("100", "StatusContinue"); + STATUS_CODE_OBJS.put("101", "StatusSwitchingProtocols"); + STATUS_CODE_OBJS.put("102", "StatusProcessing"); + STATUS_CODE_OBJS.put("103", "StatusEarlyHints"); + STATUS_CODE_OBJS.put("200", "StatusOK"); + STATUS_CODE_OBJS.put("201", "StatusCreated"); + STATUS_CODE_OBJS.put("202", "StatusAccepted"); + STATUS_CODE_OBJS.put("203", "StatusNonAuthoritativeInformation"); + STATUS_CODE_OBJS.put("204", "StatusNoContent"); + STATUS_CODE_OBJS.put("205", "StatusResetContent"); + STATUS_CODE_OBJS.put("206", "StatusPartialContent"); + STATUS_CODE_OBJS.put("207", "StatusMultiStatus"); + STATUS_CODE_OBJS.put("208", "StatusAlreadyReported"); + STATUS_CODE_OBJS.put("226", "StatusIMUsed"); + STATUS_CODE_OBJS.put("300", "StatusMultipleChoices"); + STATUS_CODE_OBJS.put("301", "StatusMovedPermanently"); + STATUS_CODE_OBJS.put("302", "StatusFound"); + STATUS_CODE_OBJS.put("303", "StatusSeeOther"); + STATUS_CODE_OBJS.put("304", "StatusNotModified"); + STATUS_CODE_OBJS.put("305", "StatusUseProxy"); + STATUS_CODE_OBJS.put("307", "StatusTemporaryRedirect"); + STATUS_CODE_OBJS.put("308", "StatusPermanentRedirect"); + STATUS_CODE_OBJS.put("400", "StatusBadRequest"); + STATUS_CODE_OBJS.put("401", "StatusUnauthorized"); + STATUS_CODE_OBJS.put("402", "StatusPaymentRequired"); + STATUS_CODE_OBJS.put("403", "StatusForbidden"); + STATUS_CODE_OBJS.put("404", "StatusNotFound"); + STATUS_CODE_OBJS.put("405", "StatusMethodNotAllowed"); + STATUS_CODE_OBJS.put("406", "StatusNotAcceptable"); + STATUS_CODE_OBJS.put("407", "StatusProxyAuthenticationRequired"); + STATUS_CODE_OBJS.put("408", "StatusRequestTimeout"); + STATUS_CODE_OBJS.put("409", "StatusConflict"); + STATUS_CODE_OBJS.put("410", "StatusGone"); + STATUS_CODE_OBJS.put("411", "StatusLengthRequired"); + STATUS_CODE_OBJS.put("412", "StatusPreconditionFailed"); + STATUS_CODE_OBJS.put("413", "StatusPayloadTooLarge"); + STATUS_CODE_OBJS.put("414", "StatusUriTooLong"); + STATUS_CODE_OBJS.put("415", "StatusUnsupportedMediaType"); + STATUS_CODE_OBJS.put("416", "StatusRangeNotSatisfiable"); + STATUS_CODE_OBJS.put("417", "StatusExpectationFailed"); + STATUS_CODE_OBJS.put("421", "StatusMisdirectedRequest"); + STATUS_CODE_OBJS.put("422", "StatusUnprocessableEntity"); + STATUS_CODE_OBJS.put("423", "StatusLocked"); + STATUS_CODE_OBJS.put("424", "StatusFailedDependency"); + STATUS_CODE_OBJS.put("425", "StatusTooEarly"); + STATUS_CODE_OBJS.put("426", "StatusUpgradeRequired"); + STATUS_CODE_OBJS.put("428", "StatusPreconditionRequired"); + STATUS_CODE_OBJS.put("429", "StatusTooManyRequests"); + STATUS_CODE_OBJS.put("431", "StatusRequestHeaderFieldsTooLarge"); + STATUS_CODE_OBJS.put("451", "StatusUnavailableDueToLegalReasons"); + STATUS_CODE_OBJS.put("500", "StatusInternalServerError"); + STATUS_CODE_OBJS.put("501", "StatusNotImplemented"); + STATUS_CODE_OBJS.put("502", "StatusBadGateway"); + STATUS_CODE_OBJS.put("503", "StatusServiceUnavailable"); + STATUS_CODE_OBJS.put("504", "StatusGatewayTimeout"); + STATUS_CODE_OBJS.put("505", "StatusHttpVersionNotSupported"); + STATUS_CODE_OBJS.put("506", "StatusVariantAlsoNegotiates"); + STATUS_CODE_OBJS.put("507", "StatusInsufficientStorage"); + STATUS_CODE_OBJS.put("508", "StatusLoopDetected"); + STATUS_CODE_OBJS.put("510", "StatusNotExtended"); + STATUS_CODE_OBJS.put("511", "StatusNetworkAuthenticationRequired"); } private ExternResponseProcessor() { @@ -135,8 +146,8 @@ public static Object processResponse(Environment env, BObject response, BTypedes private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, Runtime runtime) { - long responseStatusCode = response.getIntValue(StringUtils.fromString(STATUS_CODE)); - Optional statusCodeResponseType = getStatusCodeResponseType(targetType, + long responseStatusCode = getStatusCode(response); + Optional statusCodeResponseType = getStatusCodeResponseType(targetType, Long.toString(responseStatusCode)); if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { @@ -157,18 +168,22 @@ private static Object generatePayload(BObject response, Type targetType, boolean if (hasHttpResponseType(targetType)) { return response; } - return HttpUtil.createHttpError("incompatible " + targetType + " found for response with " + - response.getIntValue(StringUtils.fromString(STATUS_CODE)), PAYLOAD_BINDING_CLIENT_ERROR, e); + return HttpUtil.createHttpError(String.format(INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE, targetType, + getStatusCode(response)), PAYLOAD_BINDING_CLIENT_ERROR, e); } } + private static long getStatusCode(BObject response) { + return response.getIntValue(StringUtils.fromString(STATUS_CODE)); + } + private static Object generateStatusCodeResponseType(BObject response, boolean requireValidation, Runtime runtime, RecordType statusCodeRecordType, long responseStatusCode) { BMap statusCodeRecord = ValueCreator.createRecordValue(statusCodeRecordType); String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { - throw HttpUtil.createHttpError("unsupported status code: " + responseStatusCode); + throw HttpUtil.createHttpError(String.format(UNSUPPORTED_STATUS_CODE, responseStatusCode)); } populateStatusCodeObject(statusCodeObjName, statusCodeRecord); @@ -216,7 +231,7 @@ private static void populateStatusCodeObject(String statusCodeObjName, BMap anydataTypes = extractAnydataTypes(targetType, new ArrayList<>()); if (anydataTypes.isEmpty()) { - throw ErrorCreator.createError(StringUtils.fromString("no 'anydata' type found in the target type")); + throw ErrorCreator.createError(StringUtils.fromString(NO_ANYDATA_TYPE_FOUND_IN_THE_TARGET_TYPE)); } else if (anydataTypes.size() == 1) { return anydataTypes.get(0); } else { @@ -249,9 +264,9 @@ private static Object getMediaType(BObject response, Type mediaTypeType, boolean try { Object convertedValue = ValueUtils.convert(Objects.nonNull(contentType) ? StringUtils.fromString(contentType) : null, mediaTypeType); - return validateConstraints(requireValidation, convertedValue, mediaTypeType, "media-type binding failed"); + return validateConstraints(requireValidation, convertedValue, mediaTypeType, MEDIA_TYPE_BINDING_FAILED); } catch (BError conversionError) { - throw HttpUtil.createHttpError("media-type binding failed", CLIENT_ERROR, conversionError); + throw HttpUtil.createHttpError(MEDIA_TYPE_BINDING_FAILED, CLIENT_ERROR, conversionError); } } @@ -273,10 +288,10 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean } else if (headersImpliedType.getTag() == TypeTags.RECORD_TYPE_TAG) { headerMap = createHeaderRecord(httpHeaders, (RecordType) headersImpliedType); } else { - throw HttpUtil.createHttpError("unsupported headers type: " + headersType); + throw HttpUtil.createHttpError(String.format(UNSUPPORTED_HEADERS_TYPE, headersType)); } Object convertedHeaderMap = ValueUtils.convert(headerMap, headersType); - return validateConstraints(requireValidation, convertedHeaderMap, headersType, "header binding failed"); + return validateConstraints(requireValidation, convertedHeaderMap, headersType, HEADER_BINDING_FAILED); } private static boolean hasHttpResponseType(Type targetType) { @@ -295,13 +310,8 @@ private static Optional getStatusCodeResponseType(Type targetType, String return Optional.of(targetType); } } else if (targetType instanceof UnionType unionType) { - List memberTypes = unionType.getMemberTypes(); - for (Type memberType : memberTypes) { - Optional statusCodeResponseType = getStatusCodeResponseType(memberType, statusCode); - if (statusCodeResponseType.isPresent()) { - return statusCodeResponseType; - } - } + return unionType.getMemberTypes().stream().map(member -> getStatusCodeResponseType(member, statusCode)). + filter(Optional::isPresent).findFirst().orElse(Optional.empty()); } else if (targetType instanceof ReferenceType && (!targetType.equals(TypeUtils.getImpliedType(targetType)))) { return getStatusCodeResponseType(TypeUtils.getImpliedType(targetType), statusCode); @@ -480,7 +490,7 @@ public void notifyFailure(BError result) { paramFeed[1] = true; paramFeed[2] = requireValidation; paramFeed[3] = true; - runtime.invokeMethodAsyncSequentially(response, "performDataBinding", null, + runtime.invokeMethodAsyncSequentially(response, PERFORM_DATA_BINDING, null, ModuleUtils.getNotifySuccessMetaData(), returnCallback, null, PredefinedTypes.TYPE_ANY, paramFeed); try { @@ -488,7 +498,7 @@ public void notifyFailure(BError result) { return payload[0]; } catch (InterruptedException exception) { Thread.currentThread().interrupt(); - return HttpUtil.createHttpError("payload binding failed", CLIENT_ERROR, HttpUtil.createError(exception)); + return HttpUtil.createHttpError(PAYLOAD_BINDING_FAILED, CLIENT_ERROR, HttpUtil.createError(exception)); } } @@ -508,14 +518,14 @@ public void notifyFailure(BError result) { countDownLatch.countDown(); } }; - runtime.invokeMethodAsyncSequentially(response, "getApplicationResponseError", null, + 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 HttpUtil.createHttpError("http:ApplicationResponseError creation failed", + return HttpUtil.createHttpError(APPLICATION_RES_ERROR_CREATION_FAILED, PAYLOAD_BINDING_CLIENT_ERROR, HttpUtil.createError(exception)); } } From e0686d1349a324e03547df3ccea8e43e42fe8bdb Mon Sep 17 00:00:00 2001 From: ayeshLK Date: Thu, 4 Apr 2024 11:27:32 +0530 Subject: [PATCH 20/30] Refactor Optional usage with streams --- .../http/api/nativeimpl/ExternResponseProcessor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 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 8ea6374cf1..f2ea3d098b 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 @@ -310,8 +310,11 @@ private static Optional getStatusCodeResponseType(Type targetType, String return Optional.of(targetType); } } else if (targetType instanceof UnionType unionType) { - return unionType.getMemberTypes().stream().map(member -> getStatusCodeResponseType(member, statusCode)). - filter(Optional::isPresent).findFirst().orElse(Optional.empty()); + return unionType.getMemberTypes().stream() + .map(member -> getStatusCodeResponseType(member, statusCode)) + .filter(Optional::isPresent) + .flatMap(Optional::stream) + .findFirst(); } else if (targetType instanceof ReferenceType && (!targetType.equals(TypeUtils.getImpliedType(targetType)))) { return getStatusCodeResponseType(TypeUtils.getImpliedType(targetType), statusCode); From da225d53bc7d018eb36e783e80d6b9a55c11f81b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 16:28:51 +0530 Subject: [PATCH 21/30] Introduce proper errors for status code binding --- ballerina/http_errors.bal | 35 ++++-- .../stdlib/http/api/HttpErrorType.java | 12 +- .../nativeimpl/ExternResponseProcessor.java | 106 +++++++++++------- .../service/signature/AllHeaderParams.java | 16 +-- .../api/service/signature/HeaderParam.java | 4 +- 5 files changed, 109 insertions(+), 64 deletions(-) diff --git a/ballerina/http_errors.bal b/ballerina/http_errors.bal index 7a17a457d6..7d15fd6d85 100644 --- a/ballerina/http_errors.bal +++ b/ballerina/http_errors.bal @@ -50,9 +50,15 @@ public type ClientError distinct Error; # Represents a header not found error when retrieving headers. public type HeaderNotFoundError distinct Error; +# Represents an error, which occurred due to header binding. +public type HeaderBindingError distinct Error; + # Represents an error, which occurred due to payload binding. public type PayloadBindingError distinct Error; +# Represents an error, which occurred due to media-type binding. +public type MediaTypeBindingError distinct Error; + // Level 3 # Defines the listener error types that returned while receiving inbound request. public type InboundRequestError distinct ListenerError; @@ -68,17 +74,18 @@ public type InterceptorReturnError distinct ListenerError & httpscerr:InternalSe type InternalInterceptorReturnError InterceptorReturnError & InternalError; -# Represents an error, which occurred due to a header binding. -public type HeaderBindingError distinct ListenerError & httpscerr:BadRequestError; +type HeaderNotFoundClientError ClientError & HeaderNotFoundError; -type InternalHeaderBindingError HeaderBindingError & InternalError; +type HeaderBindingClientError ClientError & HeaderBindingError; + +type InternalHeaderBindingListenerError ListenerError & HeaderBindingError & httpscerr:BadRequestError & InternalError; -// TODO: Change the error type as HeaderBindingError once this issue is fixed: -// https://github.com/ballerina-platform/ballerina-lang/issues/40273 # Represents an error, which occurred due to a header constraint validation. -public type HeaderValidationError distinct HeaderBindingError & httpscerr:BadRequestError; +public type HeaderValidationError distinct HeaderBindingError; + +type HeaderValidationClientError ClientError & HeaderValidationError; -type InternalHeaderValidationError HeaderValidationError & InternalError; +type InternalHeaderValidationListenerError ListenerError & HeaderValidationError & httpscerr:BadRequestError & InternalError; # Represents an error, which occurred due to the absence of the payload. public type NoContentError distinct ClientError; @@ -99,10 +106,8 @@ public type QueryParameterBindingError distinct ListenerError & httpscerr:BadReq type InternalQueryParameterBindingError QueryParameterBindingError & InternalError; -// TODO: Change the error type as QueryParameterBindingError once this issue is fixed: -// https://github.com/ballerina-platform/ballerina-lang/issues/40273 # Represents an error, which occurred due to a query parameter constraint validation. -public type QueryParameterValidationError distinct QueryParameterBindingError & httpscerr:BadRequestError; +public type QueryParameterValidationError distinct QueryParameterBindingError; type InternalQueryParameterValidationError QueryParameterValidationError & InternalError; @@ -111,6 +116,13 @@ public type PathParameterBindingError distinct ListenerError & httpscerr:BadRequ type InternalPathParameterBindingError PathParameterBindingError & InternalError; +type MediaTypeBindingClientError ClientError & MediaTypeBindingError; + +# Represents an error, which occurred due to media type validation. +public type MediaTypeValidationError distinct MediaTypeBindingError; + +type MediaTypeValidationClientError ClientError & MediaTypeValidationError; + # Represents an error, which occurred during the request dispatching. public type RequestDispatchingError distinct ListenerError; @@ -294,3 +306,6 @@ type InternalRequestNotAcceptableError RequestNotAcceptableError & InternalError public type ResourceDispatchingServerError httpscerr:InternalServerErrorError & ResourceDispatchingError; type InternalResourceDispatchingServerError ResourceDispatchingServerError & InternalError; + +# Represents the client status code binding error +public type StatusCodeRecordBindingError distinct ClientError; 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 8b91e75fa8..e04405e5dd 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 @@ -66,7 +66,7 @@ public enum HttpErrorType { PAYLOAD_BINDING_CLIENT_ERROR("PayloadBindingClientError"), INTERNAL_PAYLOAD_BINDING_LISTENER_ERROR("InternalPayloadBindingListenerError"), INTERNAL_PAYLOAD_VALIDATION_LISTENER_ERROR("InternalPayloadValidationListenerError"), - INTERNAL_HEADER_BINDING_ERROR("InternalHeaderBindingError"), + INTERNAL_HEADER_BINDING_LISTENER_ERROR("InternalHeaderBindingListenerError"), INTERNAL_QUERY_PARAM_BINDING_ERROR("InternalQueryParameterBindingError"), INTERNAL_PATH_PARAM_BINDING_ERROR("InternalPathParameterBindingError"), INTERNAL_INTERCEPTOR_RETURN_ERROR("InternalInterceptorReturnError"), @@ -82,8 +82,14 @@ public enum HttpErrorType { INTERNAL_LISTENER_AUTHN_ERROR("InternalListenerAuthnError"), CLIENT_CONNECTOR_ERROR("ClientConnectorError"), INTERNAL_RESOURCE_PATH_VALIDATION_ERROR("InternalResourcePathValidationError"), - INTERNAL_HEADER_VALIDATION_ERROR("InternalHeaderValidationError"), - INTERNAL_QUERY_PARAM_VALIDATION_ERROR("InternalQueryParameterValidationError"); + INTERNAL_HEADER_VALIDATION_LISTENER_ERROR("InternalHeaderValidationListenerError"), + INTERNAL_QUERY_PARAM_VALIDATION_ERROR("InternalQueryParameterValidationError"), + STATUS_CODE_RECORD_BINDING_ERROR("StatusCodeRecordBindingError"), + HEADER_NOT_FOUND_CLIENT_ERROR("HeaderNotFoundClientError"), + HEADER_BINDING_CLIENT_ERROR("HeaderBindingClientError"), + HEADER_VALIDATION_CLIENT_ERROR("HeaderValidationClientError"), + MEDIA_TYPE_BINDING_CLIENT_ERROR("MediaTypeBindingClientError"), + MEDIA_TYPE_VALIDATION_CLIENT_ERROR("MediaTypeValidationClientError"); private final String errorName; 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 f2ea3d098b..bd7861edc0 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 @@ -28,12 +28,12 @@ import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; import io.ballerina.stdlib.constraint.Constraints; +import io.ballerina.stdlib.http.api.HttpErrorType; import io.ballerina.stdlib.http.api.HttpUtil; import io.ballerina.stdlib.http.api.ValueCreatorUtils; import io.netty.handler.codec.http.HttpHeaders; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,8 +50,13 @@ import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_STATUS_CODE_FIELD; import static io.ballerina.stdlib.http.api.HttpConstants.STATUS_CODE_RESPONSE_STATUS_FIELD; import static io.ballerina.stdlib.http.api.HttpErrorType.CLIENT_ERROR; -import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_BINDING_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_BINDING_CLIENT_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_NOT_FOUND_CLIENT_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_VALIDATION_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; public final class ExternResponseProcessor { @@ -168,7 +173,7 @@ private static Object generatePayload(BObject response, Type targetType, boolean if (hasHttpResponseType(targetType)) { return response; } - return HttpUtil.createHttpError(String.format(INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE, targetType, + return createHttpError(String.format(INCOMPATIBLE_TYPE_FOUND_FOR_RESPONSE, targetType, getStatusCode(response)), PAYLOAD_BINDING_CLIENT_ERROR, e); } } @@ -183,14 +188,24 @@ private static Object generateStatusCodeResponseType(BObject response, boolean r String statusCodeObjName = STATUS_CODE_OBJS.get(Long.toString(responseStatusCode)); if (Objects.isNull(statusCodeObjName)) { - throw HttpUtil.createHttpError(String.format(UNSUPPORTED_STATUS_CODE, responseStatusCode)); + return createHttpError(String.format(UNSUPPORTED_STATUS_CODE, responseStatusCode), + STATUS_CODE_RECORD_BINDING_ERROR); } populateStatusCodeObject(statusCodeObjName, statusCodeRecord); - populatedHeaders(response, requireValidation, statusCodeRecordType, statusCodeRecord); + + Object headerMap = getHeaders(response, requireValidation, statusCodeRecordType); + if (headerMap instanceof BError) { + return headerMap; + } + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD)) { - populateMediaType(response, requireValidation, statusCodeRecordType, statusCodeRecord); + Object mediaType = getMediaType(response, requireValidation, statusCodeRecordType); + if (mediaType instanceof BError) { + return mediaType; + } + statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); } if (statusCodeRecordType.getFields().containsKey(STATUS_CODE_RESPONSE_BODY_FIELD)) { @@ -209,18 +224,14 @@ private static Object getBody(BObject response, boolean requireValidation, Runti return getPayload(runtime, response, bodyType, requireValidation); } - private static void populatedHeaders(BObject response, boolean requireValidation, RecordType statusCodeRecordType, - BMap statusCodeRecord) { + private static Object getHeaders(BObject response, boolean requireValidation, RecordType statusCodeRecordType) { Type headersType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_HEADERS_FIELD).getFieldType(); - Object headerMap = getHeadersMap(response, headersType, requireValidation); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_HEADERS_FIELD), headerMap); + return getHeadersMap(response, headersType, requireValidation); } - private static void populateMediaType(BObject response, boolean requireValidation, RecordType statusCodeRecordType, - BMap statusCodeRecord) { + private static Object getMediaType(BObject response, boolean requireValidation, RecordType statusCodeRecordType) { Type mediaTypeType = statusCodeRecordType.getFields().get(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD).getFieldType(); - Object mediaType = getMediaType(response, mediaTypeType, requireValidation); - statusCodeRecord.put(StringUtils.fromString(STATUS_CODE_RESPONSE_MEDIA_TYPE_FIELD), mediaType); + return getMediaType(response, mediaTypeType, requireValidation); } private static void populateStatusCodeObject(String statusCodeObjName, BMap statusCodeRecord) { @@ -264,9 +275,10 @@ private static Object getMediaType(BObject response, Type mediaTypeType, boolean try { Object convertedValue = ValueUtils.convert(Objects.nonNull(contentType) ? StringUtils.fromString(contentType) : null, mediaTypeType); - return validateConstraints(requireValidation, convertedValue, mediaTypeType, MEDIA_TYPE_BINDING_FAILED); + return validateConstraints(requireValidation, convertedValue, mediaTypeType, + MEDIA_TYPE_VALIDATION_CLIENT_ERROR, MEDIA_TYPE_BINDING_FAILED); } catch (BError conversionError) { - throw HttpUtil.createHttpError(MEDIA_TYPE_BINDING_FAILED, CLIENT_ERROR, conversionError); + return createHttpError(MEDIA_TYPE_BINDING_FAILED, MEDIA_TYPE_VALIDATION_CLIENT_ERROR, conversionError); } } @@ -281,6 +293,7 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean if (headersImpliedType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { headersImpliedType = TypeUtils.getReferredType(headersImpliedType); } + Object headerMap; if (headersImpliedType.getTag() == TypeTags.MAP_TAG) { headerMap = createHeaderMap(httpHeaders, @@ -288,10 +301,21 @@ private static Object getHeadersMap(BObject response, Type headersType, boolean } else if (headersImpliedType.getTag() == TypeTags.RECORD_TYPE_TAG) { headerMap = createHeaderRecord(httpHeaders, (RecordType) headersImpliedType); } else { - throw HttpUtil.createHttpError(String.format(UNSUPPORTED_HEADERS_TYPE, headersType)); + return createHttpError(String.format(UNSUPPORTED_HEADERS_TYPE, headersType), + STATUS_CODE_RECORD_BINDING_ERROR); + } + + if (headerMap instanceof BError) { + return headerMap; + } + + try { + Object convertedHeaderMap = ValueUtils.convert(headerMap, headersType); + return validateConstraints(requireValidation, convertedHeaderMap, headersType, + HEADER_VALIDATION_CLIENT_ERROR, HEADER_BINDING_FAILED); + } catch (BError conversionError) { + return createHttpError(HEADER_BINDING_FAILED, HEADER_BINDING_CLIENT_ERROR, conversionError); } - Object convertedHeaderMap = ValueUtils.convert(headerMap, headersType); - return validateConstraints(requireValidation, convertedHeaderMap, headersType, HEADER_BINDING_FAILED); } private static boolean hasHttpResponseType(Type targetType) { @@ -335,23 +359,23 @@ private static String getStatusCode(Type targetType) { get(STATUS_CODE_RESPONSE_STATUS_CODE_FIELD).getFieldType().getEmptyValue().toString(); } - private static Object createHeaderMap(HttpHeaders httpHeaders, Type elementType) throws BError { + private static Object createHeaderMap(HttpHeaders httpHeaders, Type elementType) { BMap headerMap = ValueCreator.createMapValue(); Set headerNames = httpHeaders.names(); for (String headerName : headerNames) { - List headerValues = httpHeaders.getAll(headerName); + List headerValues = getHeader(httpHeaders, headerName); try { Object convertedValue = convertHeaderValues(headerValues, elementType); headerMap.put(StringUtils.fromString(headerName), convertedValue); - } catch (Exception ex) { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, - String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), null, HttpUtil.createError(ex)); + } catch (BError ex) { + return HttpUtil.createHttpError(String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), + HEADER_BINDING_CLIENT_ERROR, ex); } } return headerMap; } - private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType headersType) throws BError { + private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType headersType) { Map headers = headersType.getFields(); BMap headerMap = ValueCreator.createMapValue(); for (Map.Entry header : headers.entrySet()) { @@ -361,37 +385,37 @@ private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType hea String headerName = header.getKey(); List headerValues = getHeader(httpHeaders, headerName); - if (Objects.isNull(headerValues) || headerValues.isEmpty()) { + if (headerValues.isEmpty()) { if (isOptionalHeaderField(headerField) || headerFieldType.isNilable()) { if (headerFieldType.isNilable()) { headerMap.put(StringUtils.fromString(headerName), null); } continue; } - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, - String.format(NO_HEADER_VALUE_ERROR_MSG, headerName)); + // Return Header Not Found Error + return createHttpError(String.format(NO_HEADER_VALUE_ERROR_MSG, headerName), + HEADER_NOT_FOUND_CLIENT_ERROR); } try { Object convertedValue = convertHeaderValues(headerValues, headerFieldType); headerMap.put(StringUtils.fromString(headerName), convertedValue); - } catch (Exception ex) { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, - String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), null, HttpUtil.createError(ex)); + } catch (BError ex) { + return createHttpError(String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), + HEADER_BINDING_CLIENT_ERROR, ex); } } return headerMap; } private static BArray parseHeaderValue(List header) { - List parsedValues; + Object[] parsedValues; try { - parsedValues = header.stream().map(JsonUtils::parse).toList(); + parsedValues = header.stream().map(JsonUtils::parse).toList().toArray(); } catch (Exception e) { - parsedValues = Collections.singletonList(header.stream().map(StringUtils::fromString)); + parsedValues = header.stream().map(StringUtils::fromString).toList().toArray(); } - return ValueCreator.createArrayValue(parsedValues.toArray(), - TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON)); + return ValueCreator.createArrayValue(parsedValues, TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON)); } static class HeaderTypeInfo { @@ -456,16 +480,16 @@ private static boolean isOptionalHeaderField(Field headerField) { } private static List getHeader(HttpHeaders httpHeaders, String headerName) { - return httpHeaders.getAll(headerName); + return httpHeaders.getAllAsString(headerName); } private static Object validateConstraints(boolean requireValidation, Object convertedValue, Type type, - String errorMsg) { + HttpErrorType errorType, String errorMsg) { if (requireValidation) { Object result = Constraints.validate(convertedValue, ValueCreator.createTypedescValue(type)); if (result instanceof BError bError) { String message = errorMsg + ": " + HttpUtil.getPrintableErrorMsg(bError); - throw HttpUtil.createHttpError(message, CLIENT_ERROR); + return createHttpError(message, errorType); } } return convertedValue; @@ -501,7 +525,7 @@ public void notifyFailure(BError result) { return payload[0]; } catch (InterruptedException exception) { Thread.currentThread().interrupt(); - return HttpUtil.createHttpError(PAYLOAD_BINDING_FAILED, CLIENT_ERROR, HttpUtil.createError(exception)); + return createHttpError(PAYLOAD_BINDING_FAILED, CLIENT_ERROR, HttpUtil.createError(exception)); } } @@ -528,7 +552,7 @@ public void notifyFailure(BError result) { return clientError[0]; } catch (InterruptedException exception) { Thread.currentThread().interrupt(); - return HttpUtil.createHttpError(APPLICATION_RES_ERROR_CREATION_FAILED, + return createHttpError(APPLICATION_RES_ERROR_CREATION_FAILED, PAYLOAD_BINDING_CLIENT_ERROR, HttpUtil.createError(exception)); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/AllHeaderParams.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/AllHeaderParams.java index 68ebac151a..e937454eea 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/AllHeaderParams.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/AllHeaderParams.java @@ -33,7 +33,7 @@ import java.util.ArrayList; import java.util.List; -import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_BINDING_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_BINDING_LISTENER_ERROR; import static io.ballerina.stdlib.http.api.service.signature.ParamUtils.castParam; import static io.ballerina.stdlib.http.api.service.signature.ParamUtils.castParamArray; @@ -80,7 +80,7 @@ public void populateFeed(HttpCarbonMessage httpCarbonMessage, Object[] paramFeed try { castedHeader = ValueUtils.convert(parsedHeader, headerParam.getOriginalType()); } catch (Exception ex) { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerParam.getHeaderName()), null, HttpUtil.createError(ex)); } @@ -96,7 +96,7 @@ public void populateFeed(HttpCarbonMessage httpCarbonMessage, Object[] paramFeed paramFeed[index] = true; continue; } else { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(NO_HEADER_VALUE_ERROR_MSG, token)); } } @@ -106,7 +106,7 @@ public void populateFeed(HttpCarbonMessage httpCarbonMessage, Object[] paramFeed paramFeed[index] = true; continue; } else { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(NO_HEADER_VALUE_ERROR_MSG, token)); } } @@ -121,7 +121,7 @@ public void populateFeed(HttpCarbonMessage httpCarbonMessage, Object[] paramFeed } castedHeaderValue = ValueUtils.convert(parsedHeaderValue, headerParam.getOriginalType()); } catch (Exception ex) { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(HEADER_BINDING_FAILED_ERROR_MSG, token), null, HttpUtil.createError(ex)); } @@ -147,7 +147,7 @@ private BMap processHeaderRecord(HeaderParam headerParam, HttpH } else if (headerParam.isNilable()) { return null; } else { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(NO_HEADER_VALUE_ERROR_MSG, key)); } } @@ -158,7 +158,7 @@ private BMap processHeaderRecord(HeaderParam headerParam, HttpH } else if (headerParam.isNilable()) { return null; } else { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(NO_HEADER_VALUE_ERROR_MSG, key)); } } @@ -171,7 +171,7 @@ private BMap processHeaderRecord(HeaderParam headerParam, HttpH recordValue.put(StringUtils.fromString(key), castParam(fieldTypeTag, headerValues.get(0))); } } catch (Exception ex) { - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_ERROR, + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_BINDING_LISTENER_ERROR, String.format(HEADER_BINDING_FAILED_ERROR_MSG, key), null, HttpUtil.createError(ex)); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/HeaderParam.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/HeaderParam.java index bd29b95806..9d4de89504 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/HeaderParam.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/HeaderParam.java @@ -31,7 +31,7 @@ import static io.ballerina.runtime.api.TypeTags.RECORD_TYPE_TAG; import static io.ballerina.stdlib.http.api.HttpConstants.HEADER_PARAM; -import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_VALIDATION_ERROR; +import static io.ballerina.stdlib.http.api.HttpErrorType.INTERNAL_HEADER_VALIDATION_LISTENER_ERROR; /** * {@code {@link HeaderParam }} represents a inbound request header parameter details. @@ -98,7 +98,7 @@ public Object validateConstraints(Object headerValue) { Object result = Constraints.validateAfterTypeConversion(headerValue, getOriginalType()); if (result instanceof BError) { String message = "header validation failed: " + HttpUtil.getPrintableErrorMsg((BError) result); - throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_VALIDATION_ERROR, message); + throw HttpUtil.createHttpStatusCodeError(INTERNAL_HEADER_VALIDATION_LISTENER_ERROR, message); } } return headerValue; From 3fa55c0c91670f571f0b254e5930927a4e3746f8 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 17:23:25 +0530 Subject: [PATCH 22/30] Add tests for payload and header binding with status code response --- .../tests/sc_res_binding_tests.bal | 162 ++++++++++++++++-- 1 file changed, 150 insertions(+), 12 deletions(-) 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 87ad3727f4..76b06b715b 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 @@ -24,6 +24,15 @@ type Album record {| string genre; |}; +type MockAlbum record {| + *Album; + string 'type = "mock"; +|}; + +type AlbumUnion1 Album|MockAlbum; + +type AlbumUnion2 MockAlbum|Album; + table key(id) albums = table [ {id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"}, {id: "2", name: "Back in Black", artist: "AC/DC", genre: "Hard Rock"}, @@ -40,6 +49,21 @@ type Headers record {| int req\-id; |}; +type ArrayHeaders record {| + string[] user\-id; + int[] req\-id; +|}; + +type ArrayHeaderWithUnion record {| + string[]|int[] user\-id; + int[]|boolean[] req\-id; +|}; + +type IntHeaders record {| + int user\-id; + int req\-id; +|}; + type AlbumNotFound record {| *http:NotFound; ErrorMessage body; @@ -52,6 +76,24 @@ type AlbumFound record {| Headers headers; |}; +type AlbumFoundMock1 record {| + *http:Ok; + Album|MockAlbum body; + Headers headers; +|}; + +type AlbumFoundMock2 record {| + *http:Ok; + AlbumUnion1 body; + Headers headers; +|}; + +type AlbumFoundMock3 record {| + *http:Ok; + AlbumUnion2 body; + Headers headers; +|}; + service /api on new http:Listener(statusCodeBindingPort2) { resource function get albums/[string id]() returns AlbumFound|AlbumNotFound { @@ -70,18 +112,6 @@ service /api on new http:Listener(statusCodeBindingPort2) { final http:Client albumClient = check new (string `localhost:${statusCodeBindingPort2}/api`); -public function main() returns error? { - Album _ = check albumClient->/albums/'1; - - AlbumFound _ = check albumClient->/albums/'1; - - Album|AlbumNotFound _ = check albumClient->/albums/'1; - - AlbumFound|AlbumNotFound _ = check albumClient->/albums/'1; - - Album|http:Response _ = check albumClient->/albums/'1; -} - @test:Config {} function testGetSuccessStatusCodeResponse() returns error? { Album album = check albumClient->/albums/'1; @@ -265,3 +295,111 @@ function testGetFailureStatusCodeResponse() returns error? { test:assertFail("Invalid response type"); } } + +@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"); + test:assertEquals(res7.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res7.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res7.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFoundMock1|AlbumFound|AlbumNotFound res8 = check albumClient->get("/albums/1"); + if res8 is AlbumFoundMock1 { + test:assertEquals(res8.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res8.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res8.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res8.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFoundMock2|AlbumFound|AlbumFoundMock1|AlbumNotFound res9 = check albumClient->/albums/'1; + if res9 is AlbumFoundMock2 { + test:assertEquals(res9.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res9.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res9.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res9.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFoundMock3|AlbumFound|AlbumFoundMock1|AlbumFoundMock2|AlbumNotFound res10 = check albumClient->get("/albums/1"); + if res10 is AlbumFoundMock3 { + test:assertEquals(res10.body, {...albums.get("1"), "type": "mock"}, "Invalid album returned"); + test:assertEquals(res10.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res10.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res10.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } +} + +@test:Config {} +function testStatusCodeBindingWithDifferentHeaders() returns error? { + record {|*http:Ok; ArrayHeaders headers;|} res1 = check albumClient->/albums/'1; + test:assertEquals(res1?.body, albums.get("1"), "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"); + + record {|*http:Ok; ArrayHeaderWithUnion headers;|} res2 = check albumClient->/albums/'1; + test:assertEquals(res2?.body, albums.get("1"), "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"); + + record {|*http:Ok; IntHeaders headers;|}|error res3 = albumClient->/albums/'1; + if res3 is error { + test:assertTrue(res3 is http:HeaderBindingError); + test:assertEquals(res3.message(), "header binding failed for parameter: 'user-id'", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } +} From 0e3deaffb81c6ed7f7cfe69f91adf34f301db66b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 19:45:08 +0530 Subject: [PATCH 23/30] Add tests for media type binding and constraint validation --- .../tests/sc_res_binding_tests.bal | 274 +++++++++++++++++- .../nativeimpl/ExternResponseProcessor.java | 3 +- 2 files changed, 273 insertions(+), 4 deletions(-) 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 76b06b715b..f33da5019a 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 @@ -14,6 +14,7 @@ // specific language governing permissions and limitations // under the License. +import ballerina/constraint; import ballerina/http; import ballerina/test; @@ -59,11 +60,42 @@ type ArrayHeaderWithUnion record {| int[]|boolean[] req\-id; |}; +type ReqIdUnionType int|boolean[]; + +enum UserIds { + USER1 = "user-1", + USER2 = "user-2", + USER3 = "user-3" +} + +type ArrayHeaderWithTypes record {| + UserIds user\-id; + ReqIdUnionType req\-id; +|}; + type IntHeaders record {| int user\-id; int req\-id; |}; +type AdditionalHeaders record {| + string user\-id; + int req\-id; + string content\-type; +|}; + +type AdditionalMissingHeaders record {| + string user\-id; + int req\-id; + string x\-content\-type; +|}; + +type AdditionalOptionalHeaders record {| + string user\-id; + int req\-id; + string x\-content\-type?; +|}; + type AlbumNotFound record {| *http:NotFound; ErrorMessage body; @@ -94,6 +126,104 @@ type AlbumFoundMock3 record {| Headers headers; |}; +type AlbumInvalid record {| + *Album; + string invalidField; +|}; + +type AlbumFoundInvalid record {| + *http:Ok; + AlbumInvalid body; + Headers headers; +|}; + +enum AllowedMediaTypes { + APPLICATION_JSON = "application/json", + APPLICATION_XML = "application/xml" +} + +@constraint:String {pattern: re `application/json|application/xml`} +type MediaTypeWithConstraint string; + +type AlbumFoundWithConstraints record {| + *http:Ok; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string id; + string name; + string artist; + @constraint:String {pattern: re `Hard Rock|Progressive Rock`} + string genre; + |} body; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string user\-id; + @constraint:Int {minValue: 1, maxValue: 10} + int req\-id; + |} headers; + MediaTypeWithConstraint mediaType; +|}; + +type AlbumFoundWithInvalidConstraints1 record {| + *http:Ok; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string id; + string name; + string artist; + @constraint:String {pattern: re `Hard-Rock|Progressive-Rock`} + string genre; + |} body; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string user\-id; + @constraint:Int {minValue: 1, maxValue: 10} + int req\-id; + |} headers; + MediaTypeWithConstraint mediaType; +|}; + +type AlbumFoundWithInvalidConstraints2 record {| + *http:Ok; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string id; + string name; + string artist; + @constraint:String {pattern: re `Hard Rock|Progressive Rock`} + string genre; + |} body; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string user\-id; + @constraint:Int {minValue: 10} + int req\-id; + |} headers; + MediaTypeWithConstraint mediaType; +|}; + +@constraint:String {pattern: re `application+org/json|application/xml`} +type MediaTypeWithInvalidPattern string; + +type AlbumFoundWithInvalidConstraints3 record {| + *http:Ok; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string id; + string name; + string artist; + @constraint:String {pattern: re `Hard Rock|Progressive Rock`} + string genre; + |} body; + record {| + @constraint:String {minLength: 1, maxLength: 10} + string user\-id; + @constraint:Int {minValue: 1, maxValue: 10} + int req\-id; + |} headers; + MediaTypeWithInvalidPattern mediaType; +|}; + service /api on new http:Listener(statusCodeBindingPort2) { resource function get albums/[string id]() returns AlbumFound|AlbumNotFound { @@ -379,6 +509,15 @@ function testUnionPayloadBindingWithStatusCodeResponse() returns error? { } else { test:assertFail("Invalid response type"); } + + AlbumFoundInvalid|AlbumFound|AlbumNotFound|error res11 = albumClient->/albums/'1; + if res11 is error { + test:assertTrue(res11 is http:PayloadBindingError); + test:assertTrue(res11.message().includes("Payload binding failed: 'map' value cannot be" + + " converted to 'http_client_tests:AlbumInvalid"), "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } } @test:Config {} @@ -395,10 +534,139 @@ function testStatusCodeBindingWithDifferentHeaders() returns error? { test:assertEquals(res2.headers.req\-id, [1], "Invalid req-id header"); test:assertEquals(res2.mediaType, "application/json", "Invalid media type"); - record {|*http:Ok; IntHeaders headers;|}|error res3 = albumClient->/albums/'1; + record {|*http:Ok; ArrayHeaderWithTypes headers;|} res3 = check albumClient->/albums/'1; + test:assertEquals(res3?.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res3.headers.user\-id, USER1, "Invalid user-id header"); + test:assertEquals(res3.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res3.mediaType, "application/json", "Invalid media type"); + + record {|*http:Ok; IntHeaders headers;|}|error res4 = albumClient->/albums/'1; + if res4 is error { + test:assertTrue(res4 is http:HeaderBindingError); + test:assertEquals(res4.message(), "header binding failed for parameter: 'user-id'", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } + + record {|*http:Ok; AdditionalHeaders headers;|} res5 = check albumClient->/albums/'1; + test:assertEquals(res5?.body, albums.get("1"), "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.headers.content\-type, "application/json", "Invalid content-type header"); + test:assertEquals(res5.mediaType, "application/json", "Invalid media type"); + + record {|*http:Ok; AdditionalMissingHeaders headers;|}|error res6 = albumClient->/albums/'1; + if res6 is error { + test:assertTrue(res6 is http:HeaderNotFoundError); + test:assertEquals(res6.message(), "no header value found for 'x-content-type'", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } + + record {|*http:Ok; AdditionalOptionalHeaders headers;|} res7 = check albumClient->/albums/'1; + test:assertEquals(res7?.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res7.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res7.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res7.headers.x\-content\-type, (), "Invalid x-content-type header"); + test:assertEquals(res7.mediaType, "application/json", "Invalid media type"); +} + +@test:Config {} +function testStatusCodeBindingWithMediaTypes() returns error? { + record {|*http:Ok; "application/json" mediaType;|} res1 = check albumClient->/albums/'1; + test:assertEquals(res1?.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res1.mediaType, "application/json", "Invalid media type"); + map headers = res1.headers ?: {}; + test:assertEquals(headers.get("user-id"), "user-1", "Invalid user-id header"); + test:assertEquals(headers.get("req-id"), "1", "Invalid req-id header"); + + record {|*http:Ok; "application/xml"|"application/json" mediaType;|} res2 = check albumClient->/albums/'1; + test:assertEquals(res2?.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res2.mediaType, "application/json", "Invalid media type"); + headers = res2.headers ?: {}; + test:assertEquals(headers.get("user-id"), "user-1", "Invalid user-id header"); + test:assertEquals(headers.get("req-id"), "1", "Invalid req-id header"); + + record {|*http:Ok; AllowedMediaTypes mediaType;|} res3 = check albumClient->/albums/'1; + test:assertEquals(res3?.body, albums.get("1"), "Invalid album returned"); + test:assertEquals(res3.mediaType, APPLICATION_JSON, "Invalid media type"); + headers = res3.headers ?: {}; + test:assertEquals(headers.get("user-id"), "user-1", "Invalid user-id header"); + test:assertEquals(headers.get("req-id"), "1", "Invalid req-id header"); + + record {|*http:Ok; "application/xml" mediaType;|}|error res4 = albumClient->/albums/'1; + if res4 is error { + test:assertTrue(res4 is http:MediaTypeBindingError); + test:assertEquals(res4.message(), "media-type binding failed", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } +} + +@test:Config {} +function testStatusCodeBindingWithConstraintsSuccess() returns error? { + AlbumFoundWithConstraints res1 = check albumClient->/albums/'2; + test:assertEquals(res1.body, albums.get("2"), "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"); + + AlbumFoundWithConstraints|AlbumFound|Album|http:Response 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"); + 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"); + } + + AlbumFound|AlbumFoundWithConstraints res3 = check albumClient->/albums/'2; + if res3 is AlbumFound { + test:assertEquals(res3.body, albums.get("2"), "Invalid album returned"); + test:assertEquals(res3.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res3.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res3.mediaType, "application/json", "Invalid media type"); + } else { + test:assertFail("Invalid response type"); + } +} + +@test:Config {} +function testStatusCodeBindingWithConstraintsFailure() returns error? { + AlbumFoundWithInvalidConstraints1|error res1 = albumClient->/albums/'2; + if res1 is error { + test:assertTrue(res1 is http:PayloadValidationError); + test:assertEquals(res1.message(), "payload validation failed: Validation failed for " + + "'$.genre:pattern' constraint(s).", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFoundWithInvalidConstraints2|AlbumFound|error res2 = albumClient->get("/albums/2"); + if res2 is error { + test:assertTrue(res2 is http:HeaderValidationError); + test:assertEquals(res2.message(), "header binding failed: Validation failed for " + + "'$.req-id:minValue' constraint(s).", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFoundWithInvalidConstraints3|Album|error res3 = albumClient->/albums/'2; if res3 is error { - test:assertTrue(res3 is http:HeaderBindingError); - test:assertEquals(res3.message(), "header binding failed for parameter: 'user-id'", "Invalid error message"); + test:assertTrue(res3 is http:MediaTypeValidationError); + test:assertEquals(res3.message(), "media-type binding failed: Validation failed for " + + "'$:pattern' constraint(s).", "Invalid error message"); + } else { + test:assertFail("Invalid response type"); + } + + AlbumFound|AlbumFoundWithInvalidConstraints1|error res4 = albumClient->get("/albums/2"); + if res4 is AlbumFound { + test:assertEquals(res4.body, albums.get("2"), "Invalid album returned"); + test:assertEquals(res4.headers.user\-id, "user-1", "Invalid user-id header"); + test:assertEquals(res4.headers.req\-id, 1, "Invalid req-id header"); + test:assertEquals(res4.mediaType, "application/json", "Invalid media type"); } else { test:assertFail("Invalid response type"); } 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 bd7861edc0..f175492118 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 @@ -53,6 +53,7 @@ import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_BINDING_CLIENT_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_NOT_FOUND_CLIENT_ERROR; import static io.ballerina.stdlib.http.api.HttpErrorType.HEADER_VALIDATION_CLIENT_ERROR; +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; @@ -278,7 +279,7 @@ private static Object getMediaType(BObject response, Type mediaTypeType, boolean return validateConstraints(requireValidation, convertedValue, mediaTypeType, MEDIA_TYPE_VALIDATION_CLIENT_ERROR, MEDIA_TYPE_BINDING_FAILED); } catch (BError conversionError) { - return createHttpError(MEDIA_TYPE_BINDING_FAILED, MEDIA_TYPE_VALIDATION_CLIENT_ERROR, conversionError); + return createHttpError(MEDIA_TYPE_BINDING_FAILED, MEDIA_TYPE_BINDING_CLIENT_ERROR, conversionError); } } From 4777b66ff3fbc25586e820a1c31f9c7680e48aa7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 20:17:22 +0530 Subject: [PATCH 24/30] Update lang version --- build-config/resources/Ballerina.toml | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 845e650908..6732b94cc4 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/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.8.0" +distribution = "2201.9.0" export = ["http", "http.httpscerr"] [platform.java17] diff --git a/gradle.properties b/gradle.properties index f86586f35b..3248021763 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.11.0-SNAPSHOT -ballerinaLangVersion=2201.8.0 +ballerinaLangVersion=2201.9.0-20240404-200900-0a7e4c9e ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final From ce5b91c02dd9fd391a68ae80994f91cb0c20c747 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 20:30:19 +0530 Subject: [PATCH 25/30] [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 8bf7f22bc8..f05f3cb816 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.8.0" +distribution = "2201.9.0" export = ["http", "http.httpscerr"] [platform.java17] diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index e8516252c3..be615f0816 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" From 56182b6876ba4a08e70b149674a6ef08313740cb Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 20:40:37 +0530 Subject: [PATCH 26/30] [Automated] Update the native jar versions --- .../http-advanced-tests/Dependencies.toml | 3 ++- ballerina-tests/http-client-tests/Dependencies.toml | 3 ++- .../http-dispatching-tests/Dependencies.toml | 3 ++- .../http-interceptor-tests/Dependencies.toml | 3 ++- ballerina-tests/http-misc-tests/Dependencies.toml | 3 ++- .../http-resiliency-tests/Dependencies.toml | 3 ++- .../http-security-tests/Dependencies.toml | 3 ++- ballerina-tests/http-service-tests/Dependencies.toml | 3 ++- ballerina-tests/http-test-common/Dependencies.toml | 12 +++++++++++- ballerina-tests/http2-tests/Dependencies.toml | 3 ++- 10 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 029e4865b5..1b60df4e4a 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -346,6 +346,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 631480b290..f0f81991a4 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -342,6 +342,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index e371fe4128..8723bda48a 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -381,6 +381,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 39b8bac734..f325e48490 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -333,6 +333,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index d7d6f93a37..9ab6c4119b 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -342,6 +342,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index c7a4dc41ab..7338899a16 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -337,6 +337,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index a3240ec146..42a0b64ae6 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -338,6 +338,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index db72c8140e..d13b98f56a 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.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -342,6 +342,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index 020c6cfeac..158038c182 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -45,6 +45,15 @@ dependencies = [ {org = "ballerina", name = "lang.object"} ] +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + [[package]] org = "ballerina" name = "lang.error" @@ -115,6 +124,7 @@ name = "test" version = "0.0.0" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index e566d25184..d08b774e77 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.9.0-20240404-200900-0a7e4c9e" [[package]] org = "ballerina" @@ -342,6 +342,7 @@ version = "0.0.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.error"} ] modules = [ From 3a228e89a00f7c3e3a48137628252fa648335762 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 21:35:05 +0530 Subject: [PATCH 27/30] Remove unnecessary check for nilable header --- .../http/api/nativeimpl/ExternResponseProcessor.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 f175492118..ae9d7911c7 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 @@ -153,7 +153,7 @@ public static Object processResponse(Environment env, BObject response, BTypedes private static Object getResponseWithType(BObject response, Type targetType, boolean requireValidation, Runtime runtime) { long responseStatusCode = getStatusCode(response); - Optional statusCodeResponseType = getStatusCodeResponseType(targetType, + Optional statusCodeResponseType = getStatusCodeResponseType(targetType, Long.toString(responseStatusCode)); if (statusCodeResponseType.isPresent() && TypeUtils.getImpliedType(statusCodeResponseType.get()) instanceof RecordType statusCodeRecordType) { @@ -369,7 +369,7 @@ private static Object createHeaderMap(HttpHeaders httpHeaders, Type elementType) Object convertedValue = convertHeaderValues(headerValues, elementType); headerMap.put(StringUtils.fromString(headerName), convertedValue); } catch (BError ex) { - return HttpUtil.createHttpError(String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), + return createHttpError(String.format(HEADER_BINDING_FAILED_ERROR_MSG, headerName), HEADER_BINDING_CLIENT_ERROR, ex); } } @@ -387,10 +387,8 @@ private static Object createHeaderRecord(HttpHeaders httpHeaders, RecordType hea List headerValues = getHeader(httpHeaders, headerName); if (headerValues.isEmpty()) { - if (isOptionalHeaderField(headerField) || headerFieldType.isNilable()) { - if (headerFieldType.isNilable()) { - headerMap.put(StringUtils.fromString(headerName), null); - } + // Only optional is allowed at the moment + if (isOptionalHeaderField(headerField)) { continue; } // Return Header Not Found Error From 1991d4fd751a6527dfa8229a73d0408aa87632bd Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 21:36:33 +0530 Subject: [PATCH 28/30] Add license header --- .../api/nativeimpl/ExternResponseProcessor.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 ae9d7911c7..5952d7ea60 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 @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://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. + */ package io.ballerina.stdlib.http.api.nativeimpl; import io.ballerina.runtime.api.Environment; From 30f1bdca416bfde8878d2ad2a1582686f8bd1ec4 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 4 Apr 2024 21:37:49 +0530 Subject: [PATCH 29/30] Update the change log --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index e0ee8206fc..e3986ef987 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- [Add status code response binding support for the HTTP client](https://github.com/ballerina-platform/ballerina-library/issues/6100) + ### Fixed - [Address CVE-2024-29025 netty's vulnerability](https://github.com/ballerina-platform/ballerina-library/issues/6242) From ba3598681d5aead67768a68f07e88a2e22278e59 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 5 Apr 2024 14:02:39 +0530 Subject: [PATCH 30/30] Add java doc comment --- .../stdlib/http/api/nativeimpl/ExternResponseProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) 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 5952d7ea60..84b58154e2 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 @@ -76,6 +76,11 @@ import static io.ballerina.stdlib.http.api.HttpErrorType.STATUS_CODE_RECORD_BINDING_ERROR; import static io.ballerina.stdlib.http.api.HttpUtil.createHttpError; +/** + * Extern response processor to process the response and generate the response with the given target type. + * + * @since 2.11.0 + */ public final class ExternResponseProcessor { private static final String NO_HEADER_VALUE_ERROR_MSG = "no header value found for '%s'";