From 320a58622fc426b8765fbde3dbf856d9050523cb Mon Sep 17 00:00:00 2001 From: SachinAkash01 Date: Tue, 26 Nov 2024 11:26:48 +0530 Subject: [PATCH] Add laxDataBinding to payload builder --- .../stdlib/http/api/HttpConstants.java | 3 +++ .../stdlib/http/api/HttpResource.java | 7 ++++++- .../stdlib/http/api/InterceptorResource.java | 2 +- .../api/service/signature/ParamHandler.java | 12 +++++++---- .../api/service/signature/PayloadParam.java | 11 ++++++---- .../builder/AbstractPayloadBuilder.java | 14 ++++++------- .../signature/builder/ArrayBuilder.java | 6 ++++-- .../signature/builder/JsonPayloadBuilder.java | 6 ++++-- .../converter/JsonToRecordConverter.java | 20 +++++++++++++------ 9 files changed, 54 insertions(+), 27 deletions(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java index 130e87af2c..95925c2c03 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java @@ -502,6 +502,9 @@ public final class HttpConstants { public static final String ALLOW_DATA_PROJECTION = "allowDataProjection"; public static final String PARSER_AS_TYPE_OPTIONS = "Options"; + public static final String NIL_AS_OPTIONAL = "nilAsOptional"; + public static final String ABSENT_AS_NILABLE = "absentAsNilable"; + //Client Endpoint (CallerActions) public static final String CLIENT_ENDPOINT_SERVICE_URI = "url"; public static final String CLIENT_ENDPOINT_CONFIG = "config"; diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java index cf9de9d803..8687f3ec4f 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpResource.java @@ -336,6 +336,10 @@ private void setLaxDataBinding(boolean laxDataBinding) { this.laxDataBinding = laxDataBinding; } + private boolean getLaxDataBinding() { + return this.laxDataBinding; + } + private void updateLinkedResources(Object[] links) { for (Object link : links) { BMap linkMap = (BMap) link; @@ -392,7 +396,8 @@ private static void processResourceCors(HttpResource resource, HttpService servi } private void prepareAndValidateSignatureParams() { - paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, this.getConstraintValidation()); + paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, this.getConstraintValidation(), + this.getLaxDataBinding()); } @Override diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java b/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java index d3bf2c5048..9f7acb5584 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/InterceptorResource.java @@ -153,7 +153,7 @@ public static InterceptorResource buildInterceptorResource(MethodType resource, } private void prepareAndValidateSignatureParams() { - paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, false); + paramHandler = new ParamHandler(getBalResource(), this.pathParamCount, false, false); } @Override diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java index d884f7b914..1da2a5109d 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/ParamHandler.java @@ -74,6 +74,7 @@ public class ParamHandler { private final AllQueryParams queryParams = new AllQueryParams(); private final AllHeaderParams headerParams = new AllHeaderParams(); private final boolean constraintValidation; + private final boolean laxDataBinding; private static final String PARAM_ANNOT_PREFIX = "$param$."; private static final MapType MAP_TYPE = TypeCreator.createMapType( @@ -92,11 +93,13 @@ public class ParamHandler { + ANN_NAME_CACHE; public static final String QUERY_ANNOTATION = ModuleUtils.getHttpPackageIdentifier() + COLON + ANN_NAME_QUERY; - public ParamHandler(ResourceMethodType resource, int pathParamCount, boolean constraintValidation) { + public ParamHandler(ResourceMethodType resource, int pathParamCount, boolean constraintValidation, + boolean laxDataBinding) { this.resource = resource; this.pathParamCount = pathParamCount; this.paramTypes = getParameterTypes(resource); this.constraintValidation = constraintValidation; + this.laxDataBinding = laxDataBinding; populatePathParamTokens(resource, pathParamCount); populatePayloadAndHeaderParamTokens(resource); validateSignatureParams(); @@ -202,7 +205,7 @@ private void populatePayloadAndHeaderParamTokens(ResourceMethodType balResource) String key = ((BString) objKey).getValue(); if (PAYLOAD_ANNOTATION.equals(key)) { if (payloadParam == null) { - createPayloadParam(paramName, annotations, constraintValidation); + createPayloadParam(paramName, annotations, constraintValidation, laxDataBinding); } else { throw HttpUtil.createHttpError( "invalid multiple '" + PROTOCOL_HTTP + COLON + ANN_NAME_PAYLOAD + "' annotation usage"); @@ -237,8 +240,9 @@ private boolean isAllowedResourceParamAnnotation(String key) { return PAYLOAD_ANNOTATION.equals(key) || CALLER_ANNOTATION.equals(key) || HEADER_ANNOTATION.equals(key); } - private void createPayloadParam(String paramName, BMap annotations, boolean constraintValidation) { - this.payloadParam = new PayloadParam(paramName, constraintValidation); + private void createPayloadParam(String paramName, BMap annotations, boolean constraintValidation, + boolean laxDataBinding) { + this.payloadParam = new PayloadParam(paramName, constraintValidation, laxDataBinding); BMap mapValue = annotations.getMapValue(StringUtils.fromString(PAYLOAD_ANNOTATION)); Object mediaType = mapValue.get(HttpConstants.ANN_FIELD_MEDIA_TYPE); if (mediaType instanceof BString) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java index 375f289a1d..67d2bdc03f 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/PayloadParam.java @@ -59,10 +59,12 @@ public class PayloadParam implements Parameter { private final List mediaTypes = new ArrayList<>(); private Type customParameterType; private final boolean requireConstraintValidation; + private final boolean laxDataBinding; - PayloadParam(String token, boolean constraintValidation) { + PayloadParam(String token, boolean constraintValidation, boolean laxDataBinding) { this.token = token; this.requireConstraintValidation = constraintValidation; + this.laxDataBinding = laxDataBinding; } public void init(Type type, Type customParameterType, int index) { @@ -150,7 +152,8 @@ private void populateFeedWithAlreadyBuiltPayload(Object[] paramFeed, BObject inR if (actualTypeTag == TypeTags.BYTE_TAG) { paramFeed[index] = validateConstraints(dataSource); } else if (actualTypeTag == TypeTags.RECORD_TYPE_TAG) { - dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly); + dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly, + laxDataBinding); paramFeed[index] = validateConstraints(dataSource); } else { throw HttpUtil.createHttpError("incompatible element type found inside an array " + @@ -158,7 +161,7 @@ private void populateFeedWithAlreadyBuiltPayload(Object[] paramFeed, BObject inR } break; case TypeTags.RECORD_TYPE_TAG: - dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly); + dataSource = JsonToRecordConverter.convert(payloadType, inRequestEntity, readonly, laxDataBinding); paramFeed[index] = validateConstraints(dataSource); break; default: @@ -174,7 +177,7 @@ private int populateFeedWithFreshPayload(HttpCarbonMessage inboundMessage, Objec BObject inRequestEntity, int index, Type payloadType) { try { String contentType = HttpUtil.getContentTypeFromTransportMessage(inboundMessage); - AbstractPayloadBuilder payloadBuilder = getBuilder(contentType, payloadType); + AbstractPayloadBuilder payloadBuilder = getBuilder(contentType, payloadType, laxDataBinding); Object payloadBuilderValue = payloadBuilder.getValue(inRequestEntity, this.readonly); paramFeed[index] = validateConstraints(payloadBuilderValue); inboundMessage.setProperty(HttpConstants.ENTITY_OBJ, inRequestEntity); diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java index 222542c3a7..aaf97f84a7 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/AbstractPayloadBuilder.java @@ -63,9 +63,9 @@ public abstract class AbstractPayloadBuilder { */ public abstract Object getValue(BObject inRequestEntity, boolean readonly); - public static AbstractPayloadBuilder getBuilder(String contentType, Type payloadType) { + public static AbstractPayloadBuilder getBuilder(String contentType, Type payloadType, boolean laxDataBinding) { if (contentType == null || contentType.isEmpty()) { - return getBuilderFromType(payloadType); + return getBuilderFromType(payloadType, laxDataBinding); } contentType = contentType.toLowerCase(Locale.getDefault()).trim(); String baseType = HeaderUtil.getHeaderValue(contentType); @@ -78,22 +78,22 @@ public static AbstractPayloadBuilder getBuilder(String contentType, Type payload } else if (baseType.matches(OCTET_STREAM_PATTERN)) { return new BinaryPayloadBuilder(payloadType); } else if (baseType.matches(JSON_PATTERN)) { - return new JsonPayloadBuilder(payloadType); + return new JsonPayloadBuilder(payloadType, laxDataBinding); } else { - return getBuilderFromType(payloadType); + return getBuilderFromType(payloadType, laxDataBinding); } } - private static AbstractPayloadBuilder getBuilderFromType(Type payloadType) { + private static AbstractPayloadBuilder getBuilderFromType(Type payloadType, boolean laxDataBinding) { switch (payloadType.getTag()) { case STRING_TAG: return new StringPayloadBuilder(payloadType); case XML_TAG: return new XmlPayloadBuilder(payloadType); case ARRAY_TAG: - return new ArrayBuilder(payloadType); + return new ArrayBuilder(payloadType, laxDataBinding); default: - return new JsonPayloadBuilder(payloadType); + return new JsonPayloadBuilder(payloadType, laxDataBinding); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java index 0c9e0927dd..329c6b28aa 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/ArrayBuilder.java @@ -30,9 +30,11 @@ */ public class ArrayBuilder extends AbstractPayloadBuilder { private final Type payloadType; + private final boolean laxDataBinding; - public ArrayBuilder(Type payloadType) { + public ArrayBuilder(Type payloadType, boolean laxDataBinding) { this.payloadType = payloadType; + this.laxDataBinding = laxDataBinding; } @Override @@ -41,6 +43,6 @@ public Object getValue(BObject entity, boolean readonly) { if (elementType.getTag() == TypeTags.BYTE_TAG) { return new BinaryPayloadBuilder(payloadType).getValue(entity, readonly); } - return new JsonPayloadBuilder(payloadType).getValue(entity, readonly); + return new JsonPayloadBuilder(payloadType, laxDataBinding).getValue(entity, readonly); } } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java index be220443ea..37d49918be 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/builder/JsonPayloadBuilder.java @@ -33,9 +33,11 @@ */ public class JsonPayloadBuilder extends AbstractPayloadBuilder { private final Type payloadType; + private final boolean laxDataBinding; - public JsonPayloadBuilder(Type payloadType) { + public JsonPayloadBuilder(Type payloadType, boolean laxDataBinding) { this.payloadType = payloadType; + this.laxDataBinding = laxDataBinding; } @Override @@ -43,7 +45,7 @@ public Object getValue(BObject entity, boolean readonly) { // Following can be removed based on the solution of // https://github.com/ballerina-platform/ballerina-lang/issues/35780 if (isSubtypeOfAllowedType(payloadType, TypeTags.RECORD_TYPE_TAG)) { - return JsonToRecordConverter.convert(payloadType, entity, readonly); + return JsonToRecordConverter.convert(payloadType, entity, readonly, laxDataBinding); } Object bJson = EntityBodyHandler.constructJsonDataSource(entity); EntityBodyHandler.addJsonMessageDataSource(entity, bJson); diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java index 84971fdddb..fa5ef3e39d 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/signature/converter/JsonToRecordConverter.java @@ -36,6 +36,9 @@ import static io.ballerina.stdlib.http.api.HttpConstants.ALLOW_DATA_PROJECTION; import static io.ballerina.stdlib.http.api.HttpConstants.ENABLE_CONSTRAINT_VALIDATION; import static io.ballerina.stdlib.http.api.HttpConstants.PARSER_AS_TYPE_OPTIONS; +import static io.ballerina.stdlib.http.api.HttpConstants.NIL_AS_OPTIONAL; +import static io.ballerina.stdlib.http.api.HttpConstants.ABSENT_AS_NILABLE; + /** * The converter binds the JSON payload to a record. @@ -44,18 +47,18 @@ */ public class JsonToRecordConverter { - public static Object convert(Type type, BObject entity, boolean readonly) { - Object recordEntity = getRecordEntity(entity, type); + public static Object convert(Type type, BObject entity, boolean readonly, boolean laxDataBinding) { + Object recordEntity = getRecordEntity(entity, type, laxDataBinding); if (readonly && recordEntity instanceof BRefValue) { ((BRefValue) recordEntity).freezeDirect(); } return recordEntity; } - private static Object getRecordEntity(BObject entity, Type entityBodyType) { + private static Object getRecordEntity(BObject entity, Type entityBodyType, boolean laxDataBinding) { Object bjson = EntityBodyHandler.getMessageDataSource(entity) == null ? getBJsonValue(entity) : EntityBodyHandler.getMessageDataSource(entity); - Object result = getRecord(entityBodyType, bjson); + Object result = getRecord(entityBodyType, bjson, laxDataBinding); if (result instanceof BError) { throw (BError) result; } @@ -69,12 +72,17 @@ private static Object getRecordEntity(BObject entity, Type entityBodyType) { * @param bJson Represents the json value that needs to be converted * @return the relevant ballerina record or object */ - private static Object getRecord(Type entityBodyType, Object bJson) { + private static Object getRecord(Type entityBodyType, Object bJson, boolean laxDataBinding) { try { Map valueMap = new HashMap<>(); Boolean bool = Boolean.FALSE; valueMap.put(ENABLE_CONSTRAINT_VALIDATION, bool); - valueMap.put(ALLOW_DATA_PROJECTION, bool); + if (laxDataBinding) { + valueMap.put(NIL_AS_OPTIONAL, Boolean.TRUE); + valueMap.put(ABSENT_AS_NILABLE, Boolean.TRUE); + } else { + valueMap.put(ALLOW_DATA_PROJECTION, bool); + } BMap mapValue = ValueCreator.createRecordValue( io.ballerina.lib.data.ModuleUtils.getModule(), PARSER_AS_TYPE_OPTIONS, valueMap);