From 77b18d0ff81c21546daa0ece5ecc4d31210683cc Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Tue, 10 Dec 2024 13:59:25 +0530 Subject: [PATCH] adds SchemaViolation construct, validationutils to return set of violations instead of boolean --- .../common/utils/SchemaValidationUtils.java | 218 ++++++++++-------- .../common/violation/LeiaSchemaViolation.java | 7 + .../violation/LeiaSchemaViolationImpl.java | 30 +++ .../common/violation/ViolationContext.java | 34 +++ .../utils/SchemaValidationUtilsTest.java | 59 +++-- .../leia/validator/StaticSchemaValidator.java | 22 +- 6 files changed, 238 insertions(+), 132 deletions(-) create mode 100644 leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolation.java create mode 100644 leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolationImpl.java create mode 100644 leia-common/src/main/java/com/grookage/leia/common/violation/ViolationContext.java diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java index daa8a8e..44339d9 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java @@ -17,9 +17,13 @@ package com.grookage.leia.common.utils; import com.google.common.collect.Sets; -import com.grookage.leia.common.exception.SchemaValidationException; -import com.grookage.leia.common.exception.ValidationErrorCode; -import com.grookage.leia.models.attributes.*; +import com.grookage.leia.common.violation.LeiaSchemaViolation; +import com.grookage.leia.common.violation.ViolationContext; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.MapAttribute; +import com.grookage.leia.models.attributes.ObjectAttribute; +import com.grookage.leia.models.attributes.SchemaAttribute; +import com.grookage.leia.models.attributes.SchemaAttributeHandler; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaValidationType; import com.grookage.leia.models.schema.SchemaValidationVisitor; @@ -33,7 +37,7 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -44,32 +48,28 @@ public class SchemaValidationUtils { static Function, Function> assignableCheckFunction = klass -> attribute -> ClassUtils.isAssignable(klass, attribute.getType().getAssignableClass()); - static Function throwException = attribute -> { - log.error("Attribute {} of type {} not compatible with the type provided", - attribute.getName(), attribute.getType()); - throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS); - }; + private static final String TYPE_VIOLATION = "Incompatible Type, expected: %s, provided: %s"; - public boolean valid(final SchemaDetails schemaDetails, - final Class klass) { - return valid(schemaDetails.getValidationType(), schemaDetails.getAttributes(), klass); + public Set valid(final SchemaDetails schemaDetails, + final Class klass) { + return valid(schemaDetails.getValidationType(), schemaDetails.getAttributes(), klass, new ViolationContext()); } - public boolean valid(final SchemaValidationType validationType, - final Set attributes, - final Class klass) { - + public Set valid(final SchemaValidationType validationType, + final Set attributes, + final Class klass, + final ViolationContext context) { final var fields = FieldUtils.getAllFields(klass); - if (!validSchema(validationType, attributes, fields)) { - return false; - } - return attributes.stream().allMatch( - each -> validAttribute(each, fields, validationType)); + validSchema(validationType, attributes, fields, klass, context); + attributes.forEach(each -> validAttribute(each, fields, validationType, context)); + return context.getViolations(); } - private boolean validSchema(final SchemaValidationType validationType, - final Set attributes, - final List fields) { + private void validSchema(final SchemaValidationType validationType, + final Set attributes, + final List fields, + final Class klass, + final ViolationContext context) { final var fieldNames = fields.stream() .map(Field::getName) .map(String::toUpperCase) @@ -79,115 +79,145 @@ private boolean validSchema(final SchemaValidationType validationType, .map(String::toUpperCase) .collect(Collectors.toSet()); - return validationType.accept(new SchemaValidationVisitor<>() { + validationType.accept(new SchemaValidationVisitor<>() { @Override - public Boolean strict() { + public Void strict() { final var mismatchedAttributes = Sets.symmetricDifference(fieldNames, attributesListed); if (!mismatchedAttributes.isEmpty()) { - log.error( - "There seems to be a mismatch in the attributes present in the class definition and " - + "schema. [Validation Failed : MODE STRICT]. The attributes are {}", - mismatchedAttributes); + context.addViolation(String.format("%s - [STRICT] Validation: attributes not found or extra attributes :%s", + klass.getSimpleName(), mismatchedAttributes)); } - return mismatchedAttributes.isEmpty(); + return null; } @Override - public Boolean matching() { + public Void matching() { final var attributesMissing = Sets.difference(attributesListed, fieldNames); if (!attributesMissing.isEmpty()) { - log.error("Some attributes are missing in the class definition" + - "[Validation Failed : MODE MATCHING]. The attributes are {}", attributesMissing); + context.addViolation(String.format("%s - [MATCHING] Validation: Missing attributes found :%s", + klass.getSimpleName(), attributesMissing)); } - return attributesMissing.isEmpty(); + return null; } }); } - private boolean validAttribute(final SchemaAttribute attribute, - final List fields, - final SchemaValidationType validationType) { + private void validAttribute(final SchemaAttribute attribute, + final List fields, + final SchemaValidationType validationType, + final ViolationContext context) { final var field = fields.stream() .filter(each -> each.getName().equals(attribute.getName())) .findFirst().orElse(null); - return null != field && valid(validationType, attribute, field.getGenericType()); + context.pushPath(attribute.getName()); + if (field == null) { + context.addViolation("Missing Field"); + return; + } + valid(validationType, attribute, field.getGenericType(), context); + context.popPath(); } - public boolean valid(final SchemaValidationType validationType, - final SchemaAttribute attribute, - final Type type) { + private void valid(final SchemaValidationType validationType, + final SchemaAttribute attribute, + final Type type, + final ViolationContext context) { if (type instanceof Class klass) { - return valid(validationType, attribute, klass); + valid(validationType, attribute, klass, context); } else if (type instanceof ParameterizedType parameterizedType) { - return valid(validationType, attribute, parameterizedType); + valid(validationType, attribute, parameterizedType, context); } else if (type instanceof GenericArrayType arrayType) { - return valid(validationType, attribute, arrayType); + valid(validationType, attribute, arrayType, context); } else { - throw SchemaValidationException.error(ValidationErrorCode.NOT_SUPPORTED); + context.addViolation("Unsupported class type: " + type); } } - private boolean valid(final SchemaValidationType validationType, - final SchemaAttribute attribute, - final Class klass) { - return attribute.accept(new SchemaAttributeHandler<>( - assignableCheckFunction.apply(klass)) { + private void valid(final SchemaValidationType validationType, + final SchemaAttribute attribute, + final Class klass, + final ViolationContext context) { + if (!isMatchingType(klass, attribute)) { + context.addViolation(String.format(TYPE_VIOLATION, attribute.getType(), klass.getSimpleName())); + return; + } + + attribute.accept(new SchemaAttributeHandler(attribute1 -> null) { @Override - public Boolean accept(ArrayAttribute attribute) { + public Void accept(ArrayAttribute attribute) { if (klass.isArray()) { - if (attribute.getElementAttribute() == null) { - return true; - } - return valid(validationType, attribute.getElementAttribute(), klass.getComponentType()); + valid(validationType, attribute.getElementAttribute(), klass.getComponentType(), context); + return null; + } else if (!Objects.isNull(attribute.getElementAttribute())) { + context.addViolation(String.format("Missing Type arguments, expected ParameterizedType:%s", attribute.getElementAttribute().getType())); } - return ClassUtils.isAssignable(klass, Collection.class) && attribute.getElementAttribute() == null; + return null; } @Override - public Boolean accept(MapAttribute attribute) { - return ClassUtils.isAssignable(klass, Map.class) && attribute.getKeyAttribute() == null; + public Void accept(MapAttribute attribute) { + if (!Objects.isNull(attribute.getKeyAttribute()) || !Objects.isNull(attribute.getValueAttribute())) { + context.addViolation(String.format("Missing Type Arguments, expected parameterized Types key:%s value:%s", + attribute.getKeyAttribute().getType(), attribute.getValueAttribute().getType())); + } + return null; } @Override - public Boolean accept(ObjectAttribute attribute) { - // Handling plain Object.class - if (klass.equals(Object.class) && attribute.getNestedAttributes() == null) { - return true; - } - return valid(validationType, attribute.getNestedAttributes(), klass); + public Void accept(ObjectAttribute attribute) { + valid(validationType, attribute.getNestedAttributes(), klass, context); + return null; } }); } - private boolean valid(final SchemaValidationType validationType, - final SchemaAttribute attribute, - final ParameterizedType parameterizedType) { - return attribute.accept(new SchemaAttributeHandler<>(throwException) { + private void valid(final SchemaValidationType validationType, + final SchemaAttribute attribute, + final ParameterizedType parameterizedType, + final ViolationContext context) { + if (attribute instanceof ArrayAttribute arrayAttribute) { + if (arrayAttribute.getElementAttribute() == null) { + return; + } + final var typeArguments = getTypeArguments(parameterizedType); + valid(validationType, arrayAttribute.getElementAttribute(), typeArguments[0], context); + } else if (attribute instanceof MapAttribute mapAttribute) { + if (Objects.isNull(mapAttribute.getKeyAttribute()) || Objects.isNull(mapAttribute.getValueAttribute())) { + return; + } + final var typeArguments = getTypeArguments(parameterizedType); + valid(validationType, mapAttribute.getKeyAttribute(), typeArguments[0], context); + valid(validationType, mapAttribute.getValueAttribute(), typeArguments[1], context); + } else { + context.addViolation(String.format(TYPE_VIOLATION, attribute.getType(), parameterizedType)); + } + } + + private void valid(final SchemaValidationType validationType, + final SchemaAttribute attribute, + final GenericArrayType arrayType, + final ViolationContext context) { + if (attribute instanceof ArrayAttribute arrayAttribute) { + valid(validationType, arrayAttribute.getElementAttribute(), arrayType.getGenericComponentType(), context); + return; + } + context.addViolation(String.format(TYPE_VIOLATION, attribute.getType(), arrayType)); + } + + private boolean isMatchingType(final Class klass, + final SchemaAttribute attribute) { + return attribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) { @Override public Boolean accept(ArrayAttribute attribute) { - if (attribute.getElementAttribute() == null) { - return true; - } - final var rawType = (Class) parameterizedType.getRawType(); - if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { - return false; - } - final var typeArguments = getTypeArguments(parameterizedType); - return valid(validationType, attribute.getElementAttribute(), typeArguments[0]); + return klass.isArray() || ClassUtils.isAssignable(klass, Collection.class); } @Override - public Boolean accept(MapAttribute attribute) { - if (attribute.getKeyAttribute() == null) { - return true; - } - final var rawType = (Class) parameterizedType.getRawType(); - if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { + public Boolean accept(ObjectAttribute attribute) { + if (klass.equals(Object.class) && !Objects.isNull(attribute.getNestedAttributes())) { return false; } - final var typeArguments = getTypeArguments(parameterizedType); - return valid(validationType, attribute.getKeyAttribute(), typeArguments[0]) && - valid(validationType, attribute.getValueAttribute(), typeArguments[1]); + return true; } }); } @@ -195,23 +225,11 @@ public Boolean accept(MapAttribute attribute) { private Type[] getTypeArguments(final ParameterizedType parameterizedType) { final var typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 0) { - throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, - String.format("No type arguments found for %s", parameterizedType)); + throw new IllegalArgumentException("No type arguments found for " + parameterizedType); } return typeArguments; } - private boolean valid(final SchemaValidationType validationType, - final SchemaAttribute attribute, - final GenericArrayType arrayType) { - return attribute.accept(new SchemaAttributeHandler<>(throwException) { - @Override - public Boolean accept(final ArrayAttribute attribute) { - return valid(validationType, attribute.getElementAttribute(), arrayType.getGenericComponentType()); - } - }); - } - public boolean valid(final Class klass, final SchemaAttribute schemaAttribute) { return schemaAttribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) { diff --git a/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolation.java b/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolation.java new file mode 100644 index 0000000..7b09630 --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolation.java @@ -0,0 +1,7 @@ +package com.grookage.leia.common.violation; + +public interface LeiaSchemaViolation { + String message(); + + String fieldPath(); +} diff --git a/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolationImpl.java b/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolationImpl.java new file mode 100644 index 0000000..2741fed --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/violation/LeiaSchemaViolationImpl.java @@ -0,0 +1,30 @@ +package com.grookage.leia.common.violation; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LeiaSchemaViolationImpl implements LeiaSchemaViolation { + private String message; + private String fieldPath; + + @Override + public String message() { + return message; + } + + @Override + public String fieldPath() { + return fieldPath; + } + + @Override + public String toString() { + return String.format("[Violation] %s: %s", fieldPath, message); + } +} diff --git a/leia-common/src/main/java/com/grookage/leia/common/violation/ViolationContext.java b/leia-common/src/main/java/com/grookage/leia/common/violation/ViolationContext.java new file mode 100644 index 0000000..92e430e --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/violation/ViolationContext.java @@ -0,0 +1,34 @@ +package com.grookage.leia.common.violation; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +@Data +@Builder +@NoArgsConstructor +public class ViolationContext { + @Getter + private final Set violations = new HashSet<>(); + private final LinkedList path = new LinkedList<>(); + + public void addViolation(final String message) { + String fullPath = String.join(".", path); + violations.add(new LeiaSchemaViolationImpl(message, fullPath)); + } + + public void pushPath(final String element) { + path.addLast(element); + } + + public void popPath() { + if (!path.isEmpty()) { + path.removeLast(); + } + } +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java index 36daa3f..51d76d8 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java @@ -1,8 +1,19 @@ package com.grookage.leia.common.utils; import com.grookage.leia.common.exception.ValidationErrorCode; +import com.grookage.leia.common.violation.ViolationContext; import com.grookage.leia.models.ResourceHelper; -import com.grookage.leia.models.attributes.*; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.BooleanAttribute; +import com.grookage.leia.models.attributes.ByteAttribute; +import com.grookage.leia.models.attributes.DoubleAttribute; +import com.grookage.leia.models.attributes.EnumAttribute; +import com.grookage.leia.models.attributes.FloatAttribute; +import com.grookage.leia.models.attributes.IntegerAttribute; +import com.grookage.leia.models.attributes.LongAttribute; +import com.grookage.leia.models.attributes.MapAttribute; +import com.grookage.leia.models.attributes.ObjectAttribute; +import com.grookage.leia.models.attributes.StringAttribute; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaValidationType; import lombok.SneakyThrows; @@ -21,9 +32,9 @@ void testSchemaValidator() { final var schemaDetails = ResourceHelper .getResource("validSchema.json", SchemaDetails.class); Assertions.assertNotNull(schemaDetails); - Assertions.assertTrue(SchemaValidationUtils.valid(schemaDetails, ValidTestClass.class)); + Assertions.assertTrue(SchemaValidationUtils.valid(schemaDetails, ValidTestClass.class).isEmpty()); schemaDetails.setValidationType(SchemaValidationType.STRICT); - Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, ValidTestClass.class)); + Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, ValidTestClass.class).isEmpty()); } @Test @@ -33,7 +44,7 @@ void testInvalidMatchingSchema() { .getResource("validSchema.json", SchemaDetails.class); schemaDetails.setValidationType(SchemaValidationType.MATCHING); Assertions.assertNotNull(schemaDetails); - Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, InvalidTestClass.class)); + Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, InvalidTestClass.class).isEmpty()); } @Test @@ -85,26 +96,26 @@ void testParametrizedArray() { final var stringAttribute = new StringAttribute("stringAttribute", true, null); final var arrayAttribute = new ArrayAttribute("arrayAttribute", true, null, stringAttribute); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(arrayAttribute), - SetTestClass.class)); + SetTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(arrayAttribute), - ListTestClass.class)); + ListTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(arrayAttribute), - ArrayTestClass.class)); + ArrayTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertFalse(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(arrayAttribute), - RawSetTestClass.class)); + RawSetTestClass.class, new ViolationContext()).isEmpty()); } @Test void testRawArray() { final var arrayAttribute = new ArrayAttribute("arrayAttribute", true, null, null); +// Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, +// Set.of(arrayAttribute), RawSetTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, - Set.of(arrayAttribute), RawSetTestClass.class)); - Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, - Set.of(arrayAttribute), SetTestClass.class)); - Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, - Set.of(arrayAttribute), ListTestClass.class)); - Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, - Set.of(arrayAttribute), ArrayTestClass.class)); + Set.of(arrayAttribute), SetTestClass.class, new ViolationContext()).isEmpty()); +// Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, +// Set.of(arrayAttribute), ListTestClass.class, new ViolationContext()).isEmpty()); +// Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, +// Set.of(arrayAttribute), ArrayTestClass.class, new ViolationContext()).isEmpty()); } @Test @@ -113,22 +124,22 @@ void testParametrizedMap() { final var valueAttribute = new StringAttribute("valueAttribute", true, null); final var mapAttribute = new MapAttribute("mapAttribute", true, null, keyAttribute, valueAttribute); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - MapTestClass.class)); + MapTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - ConcurrentMapTestClass.class)); + ConcurrentMapTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertFalse(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - RawMapTestClass.class)); + RawMapTestClass.class, new ViolationContext()).isEmpty()); } @Test void testRawMap() { final var mapAttribute = new MapAttribute("mapAttribute", true, null, null, null); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - RawMapTestClass.class)); + RawMapTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - MapTestClass.class)); + MapTestClass.class, new ViolationContext()).isEmpty()); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(mapAttribute), - ConcurrentMapTestClass.class)); + ConcurrentMapTestClass.class, new ViolationContext()).isEmpty()); } @Test @@ -137,8 +148,8 @@ void testNestedObject() { final var schemaDetails = ResourceHelper .getResource("validNestedSchema.json", SchemaDetails.class); schemaDetails.setValidationType(SchemaValidationType.MATCHING); - Assertions.assertTrue(SchemaValidationUtils.valid(schemaDetails, ValidObjectTestClass.class)); - Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, InvalidObjectTestClass.class)); + Assertions.assertTrue(SchemaValidationUtils.valid(schemaDetails, ValidObjectTestClass.class).isEmpty()); + Assertions.assertFalse(SchemaValidationUtils.valid(schemaDetails, InvalidObjectTestClass.class).isEmpty()); } @Test @@ -147,7 +158,7 @@ void testGenericArrayType() { final var listAttribute = new ArrayAttribute("listAttribute", true, null, stringAttribute); final var arrayAttribute = new ArrayAttribute("arrayAttribute", true, null, listAttribute); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaValidationType.MATCHING, Set.of(arrayAttribute), - GenericArrayTestClass.class)); + GenericArrayTestClass.class, new ViolationContext()).isEmpty()); } enum TestEnum { diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java b/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java index 5d599b8..c8fe952 100644 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java +++ b/leia-schema-validator/src/main/java/com/grookage/leia/validator/StaticSchemaValidator.java @@ -19,6 +19,7 @@ import com.grookage.leia.common.exception.SchemaValidationException; import com.grookage.leia.common.exception.ValidationErrorCode; import com.grookage.leia.common.utils.SchemaValidationUtils; +import com.grookage.leia.common.violation.LeiaSchemaViolation; import com.grookage.leia.models.annotations.SchemaDefinition; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaKey; @@ -27,12 +28,13 @@ import lombok.extern.slf4j.Slf4j; import org.reflections.Reflections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; -import java.util.stream.Collectors; @Slf4j public class StaticSchemaValidator implements LeiaSchemaValidator { @@ -50,7 +52,7 @@ public StaticSchemaValidator(Supplier> supplier, } @SneakyThrows - private boolean validate(final SchemaKey schemaKey, Class klass) { + private Set validate(final SchemaKey schemaKey, Class klass) { final var details = supplier.get().stream() .filter(each -> each.match(schemaKey)).findFirst().orElse(null); if (null == details) { @@ -62,6 +64,7 @@ private boolean validate(final SchemaKey schemaKey, Class klass) { @Override public void start() { log.info("Starting the schema validator"); + Map> violations = new HashMap<>(); packageRoots.forEach(handlerPackage -> { final var reflections = new Reflections(handlerPackage); final var annotatedClasses = reflections.getTypesAnnotatedWith(SchemaDefinition.class); @@ -73,13 +76,15 @@ public void start() { .namespace(annotation.namespace()) .build(); klassRegistry.putIfAbsent(schemaKey, annotatedClass); - validationRegistry.putIfAbsent(schemaKey, validate(schemaKey, annotatedClass)); + final var schemaViolations = validate(schemaKey, annotatedClass); + validationRegistry.putIfAbsent(schemaKey, schemaViolations.isEmpty()); + if (!schemaViolations.isEmpty()) { + violations.putIfAbsent(schemaKey, schemaViolations); + } }); }); - final var invalidSchemas = validationRegistry.keySet().stream() - .filter(key -> !validationRegistry.get(key)).collect(Collectors.toSet()); - if (!invalidSchemas.isEmpty()) { - log.error("Found invalid schemas. Please fix the following schemas to start the bundle {}", invalidSchemas); + if (!violations.isEmpty()) { + log.error("Found invalid schemas. Please fix the following schemas to start the bundle {}", violations); throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS); } } @@ -92,7 +97,8 @@ public void stop() { @Override public boolean valid(SchemaKey schemaKey) { return validationRegistry.computeIfAbsent(schemaKey, - key -> getKlass(key).map(aClass -> validate(key, aClass)) + key -> getKlass(key) + .map(aClass -> validate(key, aClass).isEmpty()) .orElse(Boolean.FALSE)); }