mirror of
https://github.com/datahub-project/datahub.git
synced 2025-06-27 05:03:31 +00:00
feat(validation): enable validation trim options (#12712)
This commit is contained in:
parent
d1494c2252
commit
16ef1ac174
@ -231,7 +231,7 @@ public class ChangeItemImpl implements ChangeMCP {
|
||||
aspect =
|
||||
GenericRecordUtils.deserializeAspect(
|
||||
mcp.getAspect().getValue(), mcp.getAspect().getContentType(), aspectSpec);
|
||||
ValidationApiUtils.validateOrThrow(aspect);
|
||||
ValidationApiUtils.validateTrimOrThrow(aspect);
|
||||
} catch (ModelConversionException e) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
|
@ -133,7 +133,7 @@ public class MCLItemImpl implements MCLItem {
|
||||
aspect =
|
||||
GenericRecordUtils.deserializeAspect(
|
||||
mcl.getAspect().getValue(), mcl.getAspect().getContentType(), aspectSpec);
|
||||
ValidationApiUtils.validateOrThrow(aspect);
|
||||
ValidationApiUtils.validateTrimOrThrow(aspect);
|
||||
} else {
|
||||
aspect = null;
|
||||
}
|
||||
@ -144,7 +144,7 @@ public class MCLItemImpl implements MCLItem {
|
||||
mcl.getPreviousAspectValue().getValue(),
|
||||
mcl.getPreviousAspectValue().getContentType(),
|
||||
aspectSpec);
|
||||
ValidationApiUtils.validateOrThrow(prevAspect);
|
||||
ValidationApiUtils.validateTrimOrThrow(prevAspect);
|
||||
} else {
|
||||
prevAspect = null;
|
||||
}
|
||||
|
@ -36,6 +36,17 @@ public class ValidationApiUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void validateTrimOrThrow(RecordTemplate record) {
|
||||
RecordTemplateValidator.validateTrim(
|
||||
record,
|
||||
validationResult -> {
|
||||
throw new ValidationException(
|
||||
String.format(
|
||||
"Failed to validate record with class %s: %s",
|
||||
record.getClass().getName(), validationResult.getMessages().toString()));
|
||||
});
|
||||
}
|
||||
|
||||
public static void validateUrn(@Nonnull EntityRegistry entityRegistry, @Nonnull final Urn urn) {
|
||||
UrnValidationUtil.validateUrn(
|
||||
entityRegistry,
|
||||
@ -45,19 +56,6 @@ public class ValidationApiUtils {
|
||||
System.getenv().getOrDefault(STRICT_URN_VALIDATION_ENABLED, "false"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a {@link RecordTemplate} and logs a warning if validation fails.
|
||||
*
|
||||
* @param record record to be validated.ailure.
|
||||
*/
|
||||
public static void validateOrWarn(RecordTemplate record) {
|
||||
RecordTemplateValidator.validate(
|
||||
record,
|
||||
validationResult -> {
|
||||
log.warn(String.format("Failed to validate record %s against its schema.", record));
|
||||
});
|
||||
}
|
||||
|
||||
public static AspectSpec validate(EntitySpec entitySpec, String aspectName) {
|
||||
if (aspectName == null || aspectName.isEmpty()) {
|
||||
throw new UnsupportedOperationException(
|
||||
@ -95,7 +93,7 @@ public class ValidationApiUtils {
|
||||
EntityApiUtils.buildKeyAspect(entityRegistry, urn), resultFunction, validator);
|
||||
|
||||
if (aspect != null) {
|
||||
RecordTemplateValidator.validate(aspect, resultFunction, validator);
|
||||
RecordTemplateValidator.validateTrim(aspect, resultFunction, validator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ public class EntityUtils {
|
||||
// Read Validate
|
||||
systemAspects.forEach(
|
||||
systemAspect ->
|
||||
RecordTemplateValidator.validate(
|
||||
RecordTemplateValidator.validateTrim(
|
||||
systemAspect.getRecordTemplate(),
|
||||
validationFailure ->
|
||||
log.warn(
|
||||
|
@ -44,6 +44,8 @@ public class ValidationUtilsTest {
|
||||
rawMap.put("extraField", 1);
|
||||
Status status = new Status(rawMap);
|
||||
assertThrows(ValidationException.class, () -> ValidationApiUtils.validateOrThrow(status));
|
||||
// this one should work
|
||||
ValidationApiUtils.validateTrimOrThrow(status);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -8,7 +8,7 @@ import static com.linkedin.metadata.authorization.ApiOperation.CREATE;
|
||||
import static com.linkedin.metadata.authorization.ApiOperation.DELETE;
|
||||
import static com.linkedin.metadata.authorization.ApiOperation.EXISTS;
|
||||
import static com.linkedin.metadata.authorization.ApiOperation.READ;
|
||||
import static com.linkedin.metadata.entity.validation.ValidationApiUtils.validateOrThrow;
|
||||
import static com.linkedin.metadata.entity.validation.ValidationApiUtils.validateTrimOrThrow;
|
||||
import static com.linkedin.metadata.entity.validation.ValidationUtils.*;
|
||||
import static com.linkedin.metadata.resources.restli.RestliConstants.*;
|
||||
import static com.linkedin.metadata.search.utils.SearchUtils.*;
|
||||
@ -286,7 +286,7 @@ public class EntityResource extends CollectionResourceTaskTemplate<String, Entit
|
||||
}
|
||||
|
||||
try {
|
||||
validateOrThrow(entity);
|
||||
validateTrimOrThrow(entity);
|
||||
} catch (ValidationException e) {
|
||||
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, e);
|
||||
}
|
||||
@ -333,7 +333,7 @@ public class EntityResource extends CollectionResourceTaskTemplate<String, Entit
|
||||
|
||||
for (Entity entity : entities) {
|
||||
try {
|
||||
validateOrThrow(entity);
|
||||
validateTrimOrThrow(entity);
|
||||
} catch (ValidationException e) {
|
||||
throw new RestLiServiceException(HttpStatus.S_422_UNPROCESSABLE_ENTITY, e);
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ dependencies {
|
||||
testImplementation project(':test-models')
|
||||
testImplementation project(path: ':test-models', configuration: 'testDataTemplate')
|
||||
testImplementation externalDependency.testng
|
||||
testImplementation externalDependency.mockito
|
||||
testImplementation externalDependency.mockitoInline
|
||||
testImplementation project(':metadata-operation-context')
|
||||
|
||||
constraints {
|
||||
|
@ -21,6 +21,12 @@ public class RecordTemplateValidator {
|
||||
CoercionMode.NORMAL,
|
||||
UnrecognizedFieldMode.DISALLOW);
|
||||
|
||||
private static final ValidationOptions TRIM_VALIDATION_OPTIONS =
|
||||
new ValidationOptions(
|
||||
RequiredMode.CAN_BE_ABSENT_IF_HAS_DEFAULT,
|
||||
CoercionMode.NORMAL,
|
||||
UnrecognizedFieldMode.TRIM);
|
||||
|
||||
private static final UrnValidator URN_VALIDATOR = new UrnValidator();
|
||||
|
||||
/**
|
||||
@ -37,10 +43,25 @@ public class RecordTemplateValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a {@link RecordTemplate} and applies a function if validation fails. Extra fields are
|
||||
* trimmed.
|
||||
*
|
||||
* @param record record to be validated.failure.
|
||||
*/
|
||||
public static void validateTrim(
|
||||
RecordTemplate record, Consumer<ValidationResult> onValidationFailure) {
|
||||
final ValidationResult result =
|
||||
ValidateDataAgainstSchema.validate(record, TRIM_VALIDATION_OPTIONS, URN_VALIDATOR);
|
||||
if (!result.isValid()) {
|
||||
onValidationFailure.accept(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a {@link RecordTemplate} and applies a function if validation fails
|
||||
*
|
||||
* @param record record to be validated.ailure.
|
||||
* @param record record to be validated.failure.
|
||||
*/
|
||||
public static void validate(
|
||||
RecordTemplate record, Consumer<ValidationResult> onValidationFailure, Validator validator) {
|
||||
@ -51,5 +72,20 @@ public class RecordTemplateValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a {@link RecordTemplate} and applies a function if validation fails Extra fields are
|
||||
* trimmed.
|
||||
*
|
||||
* @param record record to be validated.failure.
|
||||
*/
|
||||
public static void validateTrim(
|
||||
RecordTemplate record, Consumer<ValidationResult> onValidationFailure, Validator validator) {
|
||||
final ValidationResult result =
|
||||
ValidateDataAgainstSchema.validate(record, TRIM_VALIDATION_OPTIONS, validator);
|
||||
if (!result.isValid()) {
|
||||
onValidationFailure.accept(result);
|
||||
}
|
||||
}
|
||||
|
||||
private RecordTemplateValidator() {}
|
||||
}
|
||||
|
@ -0,0 +1,208 @@
|
||||
package com.linkedin.metadata.utils;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.linkedin.data.schema.validation.ValidateDataAgainstSchema;
|
||||
import com.linkedin.data.schema.validation.ValidationOptions;
|
||||
import com.linkedin.data.schema.validation.ValidationResult;
|
||||
import com.linkedin.data.schema.validator.Validator;
|
||||
import com.linkedin.data.template.RecordTemplate;
|
||||
import java.util.function.Consumer;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class RecordTemplateValidatorTest {
|
||||
|
||||
@Mock private RecordTemplate mockRecord;
|
||||
|
||||
@Mock private ValidationResult mockValidationResult;
|
||||
|
||||
@Mock private Consumer<ValidationResult> mockValidationFailureHandler;
|
||||
|
||||
@Mock private Validator mockValidator;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_WhenValidationSucceeds_DoesNotCallFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(true);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validate(mockRecord, mockValidationFailureHandler);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler, never()).accept(any(ValidationResult.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidate_WhenValidationFails_CallsFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(false);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validate(mockRecord, mockValidationFailureHandler);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler).accept(mockValidationResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateTrim_WhenValidationSucceeds_DoesNotCallFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(true);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validateTrim(mockRecord, mockValidationFailureHandler);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler, never()).accept(any(ValidationResult.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateTrim_WhenValidationFails_CallsFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(false);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validateTrim(mockRecord, mockValidationFailureHandler);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler).accept(mockValidationResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateWithCustomValidator_WhenValidationSucceeds_DoesNotCallFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(true);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validate(mockRecord, mockValidationFailureHandler, mockValidator);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler, never()).accept(any(ValidationResult.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateWithCustomValidator_WhenValidationFails_CallsFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(false);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validate(mockRecord, mockValidationFailureHandler, mockValidator);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler).accept(mockValidationResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void
|
||||
testValidateTrimWithCustomValidator_WhenValidationSucceeds_DoesNotCallFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(true);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validateTrim(mockRecord, mockValidationFailureHandler, mockValidator);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler, never()).accept(any(ValidationResult.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateTrimWithCustomValidator_WhenValidationFails_CallsFailureHandler() {
|
||||
// Arrange
|
||||
try (var mockedStatic = mockStatic(ValidateDataAgainstSchema.class)) {
|
||||
when(mockValidationResult.isValid()).thenReturn(false);
|
||||
mockedStatic
|
||||
.when(
|
||||
() ->
|
||||
ValidateDataAgainstSchema.validate(
|
||||
any(RecordTemplate.class),
|
||||
any(ValidationOptions.class),
|
||||
any(Validator.class)))
|
||||
.thenReturn(mockValidationResult);
|
||||
|
||||
// Act
|
||||
RecordTemplateValidator.validateTrim(mockRecord, mockValidationFailureHandler, mockValidator);
|
||||
|
||||
// Assert
|
||||
verify(mockValidationFailureHandler).accept(mockValidationResult);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user