Skip to content

Commit

Permalink
Add laxDataBinding to payload builder
Browse files Browse the repository at this point in the history
  • Loading branch information
SachinAkash01 committed Nov 26, 2024
1 parent e2e7d59 commit 320a586
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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();
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ public class PayloadParam implements Parameter {
private final List<String> 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) {
Expand Down Expand Up @@ -150,15 +152,16 @@ 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 " +
((ArrayType) payloadType).getElementType().getName());
}
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:
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,19 @@
*/
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
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}
Expand All @@ -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<String, Object> 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<BString, Object> mapValue = ValueCreator.createRecordValue(
io.ballerina.lib.data.ModuleUtils.getModule(),
PARSER_AS_TYPE_OPTIONS, valueMap);
Expand Down

0 comments on commit 320a586

Please sign in to comment.