From 981f62a5ef08503051bd6103f0857a5b563510e2 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Thu, 28 Nov 2024 12:22:30 +0530 Subject: [PATCH 01/12] adds utils for building schemaAttributes and validation of json --- leia-client/pom.xml | 7 + .../leia/client/utils/SchemaUtil.java | 227 ++++++++++++++ .../leia/client/utils/SchemaUtilTest.java | 281 ++++++++++++++++++ .../leia/core/utils/ValidationUtils.java | 179 +++++++++++ .../leia/core/utils/ValidationUtilsTest.java | 202 +++++++++++++ .../bundle/resources/SchemaResource.java | 24 ++ .../leia/models/qualifiers/QualifierInfo.java | 19 ++ .../qualifiers/annotations/Qualifier.java | 16 + leia-schema-validator/pom.xml | 6 + .../utils/SchemaValidationUtils.java | 54 ++-- .../validator/SchemaValidationUtilsTest.java | 9 + 11 files changed, 1002 insertions(+), 22 deletions(-) create mode 100644 leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java create mode 100644 leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java create mode 100644 leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java create mode 100644 leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java create mode 100644 leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java diff --git a/leia-client/pom.xml b/leia-client/pom.xml index 8217c29..d5b7725 100644 --- a/leia-client/pom.xml +++ b/leia-client/pom.xml @@ -29,6 +29,7 @@ leia-client + 3.11 false @@ -53,6 +54,12 @@ org.projectlombok + + org.apache.commons + commons-lang3 + ${lang3.version} + + junit-jupiter org.junit.jupiter diff --git a/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java b/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java new file mode 100644 index 0000000..f30abae --- /dev/null +++ b/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java @@ -0,0 +1,227 @@ +package com.grookage.leia.client.utils; + +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.BooleanAttribute; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.annotations.Qualifier; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.ClassUtils; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@UtilityClass +public class SchemaUtil { + public Set buildSchemaAttributes(final Class klass) { + return getAllFields(klass) + .stream().map(SchemaUtil::schemaAttribute) + .collect(Collectors.toSet()); + } + + private SchemaAttribute schemaAttribute(final Field field) { + return schemaAttribute( + field.getGenericType(), + field.getName(), + getQualifierInfo(field), + isOptional(field) + ); + } + + private SchemaAttribute schemaAttribute(final Type type, + final String name, + final QualifierInfo qualifierInfo, + final boolean optional) { + // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) + if (type instanceof Class klass) { + return schemaAttribute(klass, name, qualifierInfo, optional); + } + + // Handle ParameterizedType (e.g., List, Map) + if (type instanceof ParameterizedType parameterizedType) { + return schemaAttribute(parameterizedType, name, qualifierInfo, optional); + } + + // Handle GenericArrayType (e.g., T[], List) + if (type instanceof GenericArrayType genericArrayType) { + return schemaAttribute(genericArrayType, name, qualifierInfo, optional); + } + + throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); + } + + private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, + final String name, + final QualifierInfo qualifierInfo, + final boolean optional) { + Class rawType = (Class) parameterizedType.getRawType(); + // Handle List or Set + if (ClassUtils.isAssignable(rawType, Collection.class)) { + return handleCollection(parameterizedType, name, qualifierInfo, optional); + } + + // Handle Map + if (ClassUtils.isAssignable(rawType, Map.class)) { + return handleMap(parameterizedType, name, qualifierInfo, optional); + } + throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); + } + + private SchemaAttribute handleMap(ParameterizedType parameterizedType, String name, QualifierInfo qualifierInfo, boolean optional) { + final var keyType = parameterizedType.getActualTypeArguments()[0]; + final var valueType = parameterizedType.getActualTypeArguments()[1]; + return new MapAttribute( + name, + optional, + qualifierInfo, + schemaAttribute(keyType, "key", getQualifierInfo(keyType), isOptional(keyType)), + schemaAttribute(valueType, "value", getQualifierInfo(valueType), isOptional(valueType)) + ); + } + + private SchemaAttribute handleCollection(ParameterizedType parameterizedType, String name, QualifierInfo qualifierInfo, boolean optional) { + final var elementType = parameterizedType.getActualTypeArguments()[0]; + return new ArrayAttribute( + name, + optional, + qualifierInfo, + schemaAttribute(elementType, "element", getQualifierInfo(elementType), isOptional(elementType)) + ); + } + + private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, + final String name, + final QualifierInfo qualifierInfo, + final boolean optional) { + final var componentType = genericArrayType.getGenericComponentType(); + return new ArrayAttribute( + name, + optional, + qualifierInfo, + schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) + ); + } + + + private SchemaAttribute schemaAttribute(final Class klass, + final String name, + QualifierInfo qualifierInfo, + final boolean optional) { + if (klass == String.class) { + return new StringAttribute(name, optional, qualifierInfo); + } + + if (klass.isEnum()) { + return new EnumAttribute(name, optional, qualifierInfo, getEnumValues(klass)); + } + + if (klass.isPrimitive()) { + return handlePrimitive(klass, name, qualifierInfo, optional); + } + + // Handle String[], Object[] etc. + if (klass.isArray()) { + final var componentType = klass.getComponentType(); + return new ArrayAttribute( + name, + optional, + qualifierInfo, + schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) + ); + } + + + // Handling custom defined POJO's + final var schemaAttributes = buildSchemaAttributes(klass); + return new ObjectAttribute(name, optional, qualifierInfo, schemaAttributes); + } + + private SchemaAttribute handlePrimitive(final Class klass, + final String name, + final QualifierInfo qualifierInfo, + final boolean optional) { + if (klass == Integer.class || klass == int.class) { + return new IntegerAttribute(name, optional, qualifierInfo); + } + if (klass == Boolean.class || klass == boolean.class) { + return new BooleanAttribute(name, optional, qualifierInfo); + } + if (klass == Double.class || klass == double.class) { + return new DoubleAttribute(name, optional, qualifierInfo); + } + if (klass == Long.class || klass == long.class) { + return new LongAttribute(name, optional, qualifierInfo); + } + if (klass == Float.class || klass == float.class) { + return new FloatAttribute(name, optional, qualifierInfo); + } + + throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); + + } + + private List getAllFields(Class type) { + List fields = new ArrayList<>(); + for (Class c = type; c != null; c = c.getSuperclass()) { + fields.addAll(Arrays.asList(c.getDeclaredFields())); + } + return fields; + } + + private Set getEnumValues(Class klass) { + return Arrays.stream(klass.getEnumConstants()) + .map(enumConstant -> ((Enum) enumConstant).name()) + .collect(Collectors.toSet()); + } + + private QualifierInfo getQualifierInfo(Field field) { + Qualifier qualifier = field.getAnnotation(Qualifier.class); + return QualifierInfo.toQualifierInfo(qualifier); + } + + private QualifierInfo getQualifierInfo(Type type) { + if (type instanceof Class klass) { + return getQualifierInfo(klass); + } + return null; + } + + private QualifierInfo getQualifierInfo(Class klass) { + return QualifierInfo.toQualifierInfo(klass.getAnnotation(Qualifier.class)); + } + + private boolean isOptional(Type type) { + if (type instanceof Class klass) { + return isOptional(klass); + } + return false; + } + + private boolean isOptional(Class klass) { + return !klass.isAnnotationPresent(NotNull.class) && !klass.isAnnotationPresent(NotEmpty.class); + } + + private boolean isOptional(Field field) { + // Check for @NotNull and @NotEmpty annotations + return !field.isAnnotationPresent(NotNull.class) && !field.isAnnotationPresent(NotEmpty.class);// Default to true if no such annotations are present + } +} diff --git a/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java b/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java new file mode 100644 index 0000000..334bc59 --- /dev/null +++ b/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java @@ -0,0 +1,281 @@ +package com.grookage.leia.client.utils; + +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.SchemaAttribute; +import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.QualifierType; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; +import com.grookage.leia.models.qualifiers.StandardQualifier; +import com.grookage.leia.models.qualifiers.annotations.Qualifier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.validation.constraints.NotEmpty; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +class SchemaUtilTest { + + @Test + void testSchemaAttributes_WithPrimitiveClass() { + final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestWithPrimitive.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", false, new StandardQualifier()); + equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithRecordClass() { + final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestRecord.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", false, new PIIQualifier()); + equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithNestedObject() { + final var schemaAttributes = SchemaUtil.buildSchemaAttributes(TestWithNested.class); + Assertions.assertFalse(schemaAttributes.isEmpty()); + Assertions.assertEquals(6, schemaAttributes.size()); + final var nameAttribute = new StringAttribute("name", true, new StandardQualifier()); + equals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); + + final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + equals(idAttribute, filter(schemaAttributes, "id").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", true, new StandardQualifier()); + final var accountNumberAttribute = new StringAttribute("accountNumber", true, new EncryptedQualifier()); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataAttribute = new ObjectAttribute("piiData", true, new PIIQualifier(), testPIIDataAttributes); + equals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); + + final var testRecordAttributes = new HashSet(); + final var recordNameAttribute = new StringAttribute("name", false, new PIIQualifier()); + final var recordIdAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + testRecordAttributes.add(recordNameAttribute); + testRecordAttributes.add(recordIdAttribute); + final var testRecordAttribute = new ObjectAttribute("testRecord", true, new EncryptedQualifier(), + testRecordAttributes); + equals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); + + final var enumClassAttribute = new EnumAttribute("enumClass", true, new StandardQualifier(), Set.of(EnumClass.ONE.name(), + EnumClass.TWO.name())); + equals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); + + final var phoneNoAttribute = new StringAttribute("phoneNumber", true, new PIIQualifier()); + equals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); + } + + @Test + void testSchemaAttributes_WithParameterizedType() { + final var schemaAttributes = SchemaUtil.buildSchemaAttributes(TestWithParameterized.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(3, schemaAttributes.size()); + + final var valuesAttributes = new ArrayAttribute("values", true, new StandardQualifier(), + new StringAttribute("element", true, new StandardQualifier())); + equals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", true, new StandardQualifier()); + final var accountNumberAttribute = new StringAttribute("accountNumber", true, new EncryptedQualifier()); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataListAttribute = new ArrayAttribute("piiDataList", true, new PIIQualifier(), + new ObjectAttribute("element", true, new PIIQualifier(), testPIIDataAttributes)); + equals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); + + final var mapAttribute = new MapAttribute("map", true, new EncryptedQualifier(), + new EnumAttribute("key", true, new StandardQualifier(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), + new StringAttribute("value", true, new StandardQualifier())); + equals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); + } + + enum EnumClass { + ONE, + TWO + } + + @Qualifier(type = QualifierType.PII) + static class TestPIIData { + String name; + @Qualifier(type = QualifierType.ENCRYPTED) + String accountNumber; + } + + static record TestRecord(@Qualifier(type = QualifierType.PII) @NotEmpty String name, + int id) { + + } + + static class TestWithPrimitive { + @NotEmpty + String name; + int id; + } + + static class TestWithNested { + String name; + int id; + @Qualifier(type = QualifierType.PII) + TestPIIData piiData; + @Qualifier(type = QualifierType.ENCRYPTED) + TestRecord testRecord; + EnumClass enumClass; + @Qualifier(type = QualifierType.PII) + String phoneNumber; + } + + static class TestWithParameterized { + String[] values; + @Qualifier(type = QualifierType.PII) + List piiDataList; + @Qualifier(type = QualifierType.ENCRYPTED) + Map map; + } + + private Optional filter(Set schemaAttributes, + String name) { + return schemaAttributes.stream() + .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) + .findFirst(); + } + + private void equals(SchemaAttribute expected, + SchemaAttribute original) { + if (Objects.isNull(expected) && Objects.isNull(original)) { + return; + } + Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch"); + Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch"); + Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); + + // Compare QualifierInfo + equals(expected.getQualifierInfo(), original.getQualifierInfo()); + + // Accept the expected attribute type and perform specific validations + expected.accept(new SchemaAttributeAcceptor() { + @Override + public Void accept(BooleanAttribute attribute) { + Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute"); + return null; + } + + @Override + public Void accept(ByteAttribute attribute) { + Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute"); + return null; + } + + @Override + public Void accept(DoubleAttribute attribute) { + Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute"); + return null; + } + + @Override + public Void accept(EnumAttribute attribute) { + Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute"); + EnumAttribute originalEnum = (EnumAttribute) original; + Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch"); + return null; + } + + @Override + public Void accept(FloatAttribute attribute) { + Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute"); + return null; + } + + @Override + public Void accept(IntegerAttribute attribute) { + Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute"); + return null; + } + + @Override + public Void accept(LongAttribute attribute) { + Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute"); + return null; + } + + @Override + public Void accept(StringAttribute attribute) { + Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute"); + return null; + } + + @Override + public Void accept(ArrayAttribute attribute) { + Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); + ArrayAttribute originalArray = (ArrayAttribute) original; + SchemaUtilTest.this.equals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute + return null; + } + + @Override + public Void accept(MapAttribute attribute) { + Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); + MapAttribute originalMap = (MapAttribute) original; + SchemaUtilTest.this.equals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key + SchemaUtilTest.this.equals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value + return null; + } + + @Override + public Void accept(ObjectAttribute attribute) { + Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute"); + ObjectAttribute originalObject = (ObjectAttribute) original; + + // Recursive comparison of nested attributes + Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(), + "Nested attributes size mismatch"); + Iterator expectedIterator = attribute.getNestedAttributes().iterator(); + Iterator originalIterator = originalObject.getNestedAttributes().iterator(); + + while (expectedIterator.hasNext() && originalIterator.hasNext()) { + SchemaUtilTest.this.equals(expectedIterator.next(), originalIterator.next()); + } + return null; + } + }); + } + + private void equals(QualifierInfo expected, + QualifierInfo original) { + if (Objects.isNull(expected) && Objects.isNull(original)) { + return; + } + Assertions.assertEquals(expected.getType(), original.getType()); + if (expected instanceof ShortLivedQualifier expectedQualifier + && original instanceof ShortLivedQualifier originalQualifier) { + Assertions.assertEquals(expectedQualifier.getTtlSeconds(), originalQualifier.getTtlSeconds()); + } + } +} \ No newline at end of file diff --git a/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java b/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java new file mode 100644 index 0000000..65324fc --- /dev/null +++ b/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java @@ -0,0 +1,179 @@ +package com.grookage.leia.core.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.grookage.leia.models.ResourceHelper; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.schema.SchemaValidationType; +import lombok.experimental.UtilityClass; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +@UtilityClass +public class ValidationUtils { + public static List validate(JsonNode jsonNode, + SchemaValidationType validationType, + Set schemaAttributes) { + List validationErrors = new ArrayList<>(); + + Map schemaMap = new HashMap<>(); + for (SchemaAttribute attribute : schemaAttributes) { + schemaMap.put(attribute.getName(), attribute); + } + + // Validate extra fields in case of Strict Validation + if (validationType == SchemaValidationType.STRICT) { + jsonNode.fieldNames().forEachRemaining(fieldName -> { + if (!schemaMap.containsKey(fieldName)) { + validationErrors.add("Unexpected field: " + fieldName); + } + }); + } + + // Validate missing and type mismatched fields + for (SchemaAttribute attribute : schemaAttributes) { + final var fieldName = attribute.getName(); + // Check the attribute only if the jsonNode is an object + if (jsonNode.isObject() && !jsonNode.has(fieldName)) { + if (!attribute.isOptional()) { + validationErrors.add("Missing required field: " + fieldName); + } + continue; + } + + if (jsonNode.isValueNode()) { + validateField(jsonNode, attribute, validationType, validationErrors); + continue; + } + JsonNode fieldNode = jsonNode.get(fieldName); + validateField(fieldNode, attribute, validationType, validationErrors); + } + + return validationErrors; + } + + private void validateField(JsonNode fieldNode, + SchemaAttribute attribute, + SchemaValidationType validationType, + List validationErrors) { + final var fieldName = attribute.getName(); + if (!isMatchingType(fieldNode, attribute)) { + validationErrors.add("Type mismatch for field: " + fieldName + + ". Expected: " + attribute.getType() + + ", Found: " + fieldNode.getNodeType()); + return; + } + + // Recursively validate nested objects + if (attribute instanceof ObjectAttribute objectAttribute) { + validationErrors.addAll(validate(fieldNode, validationType, objectAttribute.getNestedAttributes())); + return; + } + if (attribute instanceof ArrayAttribute arrayAttribute) { + for (JsonNode arrayElement : fieldNode) { + validationErrors.addAll(validate(arrayElement, validationType, Set.of(arrayAttribute.getElementAttribute()))); + } + return; + } + if (attribute instanceof MapAttribute mapAttribute) { + validateMapAttribute(fieldNode, mapAttribute, validationType, validationErrors); + } + } + + private void validateMapAttribute(JsonNode fieldNode, + MapAttribute mapAttribute, + SchemaValidationType schemaValidationType, + List validationErrors) { + fieldNode.fields().forEachRemaining(entry -> { + // Validate key + JsonNode keyNode = entry.getKey() != null + ? ResourceHelper.getObjectMapper().convertValue(entry.getKey(), JsonNode.class) + : null; + if (!Objects.isNull(keyNode)) { + // validate Key + validateField(keyNode, mapAttribute.getKeyAttribute(), schemaValidationType, validationErrors); + // Validate value + validateField(entry.getValue(), mapAttribute.getValueAttribute(), schemaValidationType, validationErrors); + } else { + validationErrors.add("Key Not present for map attribute field:" + + mapAttribute.getName()); + } + }); + + } + + private boolean isMatchingType(JsonNode fieldNode, SchemaAttribute attribute) { + return attribute.accept(new SchemaAttributeAcceptor<>() { + @Override + public Boolean accept(BooleanAttribute attribute) { + return fieldNode.isBoolean(); + } + + @Override + public Boolean accept(ByteAttribute attribute) { + return fieldNode.isArray(); + } + + @Override + public Boolean accept(DoubleAttribute attribute) { + return fieldNode.isDouble() || fieldNode.isFloat() || fieldNode.isInt(); + } + + @Override + public Boolean accept(EnumAttribute attribute) { + return fieldNode.isTextual() && attribute.getValues().contains(fieldNode.asText()); + } + + @Override + public Boolean accept(FloatAttribute attribute) { + return fieldNode.isFloat(); + } + + @Override + public Boolean accept(IntegerAttribute attribute) { + return fieldNode.isInt(); + } + + @Override + public Boolean accept(LongAttribute attribute) { + return fieldNode.isLong() || fieldNode.isInt(); + } + + @Override + public Boolean accept(StringAttribute attribute) { + return fieldNode.isTextual(); + } + + @Override + public Boolean accept(ArrayAttribute attribute) { + return fieldNode.isArray(); + } + + @Override + public Boolean accept(MapAttribute attribute) { + return fieldNode.isObject(); + } + + @Override + public Boolean accept(ObjectAttribute attribute) { + return fieldNode.isObject(); + } + }); + } +} diff --git a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java new file mode 100644 index 0000000..d9dffb5 --- /dev/null +++ b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java @@ -0,0 +1,202 @@ +package com.grookage.leia.core.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.grookage.leia.models.ResourceHelper; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.BooleanAttribute; +import com.grookage.leia.models.attributes.IntegerAttribute; +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.StringAttribute; +import com.grookage.leia.models.schema.SchemaValidationType; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ValidationUtilsTest { + @Test + void testValidJsonAgainstSchema() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "name": "John Doe", + "age": 30, + "isActive": true + } + """); + + Set schemaAttributes = Set.of( + new StringAttribute("name", false, null), + new IntegerAttribute("age", false, null), + new BooleanAttribute("isActive", false, null) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertTrue(errors.isEmpty()); + } + + @Test + void testUnexpectedFieldInJson() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "name": "John Doe", + "age": 30, + "isActive": true, + "unexpectedField": "extra" + } + """); + + Set schemaAttributes = Set.of( + new StringAttribute("name", false, null), + new IntegerAttribute("age", false, null), + new BooleanAttribute("isActive", false, null) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertFalse(errors.isEmpty()); + assertEquals(1, errors.size()); + assertEquals("Unexpected field: unexpectedField", errors.get(0)); + } + + @Test + void testMissingRequiredField() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "name": "John Doe" + } + """); + + Set schemaAttributes = Set.of( + new StringAttribute("name", false, null), + new IntegerAttribute("age", false, null) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertFalse(errors.isEmpty()); + assertEquals(1, errors.size()); + assertEquals("Missing required field: age", errors.get(0)); + } + + @Test + void testTypeMismatch() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "name": "John Doe", + "age": "thirty" + } + """); + + Set schemaAttributes = Set.of( + new StringAttribute("name", false, null), + new IntegerAttribute("age", false, null) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertFalse(errors.isEmpty()); + assertEquals(1, errors.size()); + assertEquals("Type mismatch for field: age. Expected: INTEGER, Found: STRING", errors.get(0)); + } + + @Test + void testNestedObjectValidation() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "user": { + "id": 1, + "username": "johndoe" + } + } + """); + + Set nestedAttributes = Set.of( + new IntegerAttribute("id", false, null), + new StringAttribute("username", false, null) + ); + + Set schemaAttributes = Set.of( + new ObjectAttribute("user", false, null, nestedAttributes) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertTrue(errors.isEmpty()); + } + + @Test + void testArrayValidation() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "numbers": [1, 2, 3, 4] + } + """); + + Set schemaAttributes = Set.of( + new ArrayAttribute("numbers", false, null, new IntegerAttribute("element", false, null)) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertTrue(errors.isEmpty()); + } + + @Test + void testMapValidation() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "attributes": { + "key1": "value1", + "key2": "value2" + } + } + """); + + Set schemaAttributes = Set.of( + new MapAttribute( + "attributes", + false, + null, + new StringAttribute("key", false, null), + new StringAttribute("value", false, null) + ) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertTrue(errors.isEmpty()); + } + + @Test + void testInvalidMapValueType() throws Exception { + JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + { + "attributes": { + "key1": 100 + } + } + """); + + Set schemaAttributes = Set.of( + new MapAttribute( + "attributes", + false, + null, + new StringAttribute("key", false, null), + new StringAttribute("value", false, null) + ) + ); + + List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + + assertFalse(errors.isEmpty()); + assertEquals(1, errors.size()); + } +} \ No newline at end of file diff --git a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java index 19ef220..b1209f9 100644 --- a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java +++ b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java @@ -18,7 +18,11 @@ import com.codahale.metrics.annotation.ExceptionMetered; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.databind.JsonNode; +import com.grookage.leia.core.exception.LeiaErrorCode; +import com.grookage.leia.core.exception.LeiaException; import com.grookage.leia.core.retrieval.SchemaRetriever; +import com.grookage.leia.core.utils.ValidationUtils; import com.grookage.leia.models.GenericResponse; import com.grookage.leia.models.request.NamespaceRequest; import com.grookage.leia.models.schema.SchemaDetails; @@ -78,4 +82,24 @@ public List getAllSchemaDetails(@Valid final NamespaceRequest nam return schemaRetriever.getAllSchemaDetails(namespaceRequest.getNamespaces()); } + @POST + @Timed + @ExceptionMetered + @Path("/details/validate") + public GenericResponse> validateSchema(@Valid SchemaKey schemaKey, + @Valid JsonNode jsonNode) { + final var schemaDetails = schemaRetriever.getSchemaDetails(schemaKey) + .orElseThrow(() -> LeiaException.error(LeiaErrorCode.NO_SCHEMA_FOUND)); + final var validationErrors = ValidationUtils.validate(jsonNode, schemaDetails.getValidationType(), schemaDetails.getAttributes()); + if (validationErrors.isEmpty()) { + return GenericResponse.>builder() + .success(true) + .build(); + } + return GenericResponse.>builder() + .data(validationErrors) + .build(); + + } + } diff --git a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java index a4aff87..f68fcee 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java +++ b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.grookage.leia.models.qualifiers.annotations.Qualifier; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -38,4 +39,22 @@ public abstract class QualifierInfo { private QualifierType type; + public static QualifierInfo toQualifierInfo(Qualifier qualifier) { + if (qualifier == null) { + return new StandardQualifier(); + } + + final var type = qualifier.type(); + final var ttlInSecs = qualifier.ttlSeconds(); + if (type == QualifierType.SHORT_LIVED) { + return new ShortLivedQualifier(ttlInSecs); + } else if (type == QualifierType.PII) { + return new PIIQualifier(); + } else if (type == QualifierType.ENCRYPTED) { + return new EncryptedQualifier(); + } else { + return new StandardQualifier(); + } + } + } diff --git a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java new file mode 100644 index 0000000..4da1fa6 --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java @@ -0,0 +1,16 @@ +package com.grookage.leia.models.qualifiers.annotations; + +import com.grookage.leia.models.qualifiers.QualifierType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Qualifier { + QualifierType type(); + + int ttlSeconds() default -1; // Applicable for ShortLivedQualifier +} diff --git a/leia-schema-validator/pom.xml b/leia-schema-validator/pom.xml index 2951e32..d3d5251 100644 --- a/leia-schema-validator/pom.xml +++ b/leia-schema-validator/pom.xml @@ -33,6 +33,7 @@ false 0.9.9 7.0.0 + 3.11 @@ -46,6 +47,11 @@ org.reflections ${reflections.version} + + org.apache.commons + commons-lang3 + ${lang3.version} + leia-models diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java b/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java index 076b170..5366505 100644 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java +++ b/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java @@ -17,7 +17,11 @@ package com.grookage.leia.validator.utils; import com.google.common.collect.Sets; -import com.grookage.leia.models.attributes.*; +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; @@ -25,12 +29,18 @@ import com.grookage.leia.validator.exception.ValidationErrorCode; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ClassUtils; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -38,7 +48,7 @@ @Slf4j public class SchemaValidationUtils { static Function, Function> assignableCheckFunction = - klass -> attribute -> attribute.getType().getAssignableClass().isAssignableFrom(klass); + klass -> attribute -> ClassUtils.isAssignable(klass, attribute.getType().getAssignableClass()); static Function throwException = attribute -> { log.error("Attribute {} of type {} not compatible with the type provided", @@ -46,13 +56,13 @@ public class SchemaValidationUtils { throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS); }; - public static boolean valid(final SchemaDetails schemaDetails, - final Class klass) { + public boolean valid(final SchemaDetails schemaDetails, + final Class klass) { return valid(schemaDetails.getValidationType(), schemaDetails.getAttributes(), klass); } - public static boolean valid(final SchemaValidationType validationType, - Set attributes, final Class klass) { + public boolean valid(final SchemaValidationType validationType, + Set attributes, final Class klass) { final var fields = getAllFields(klass); if (!validSchema(validationType, attributes, fields)) { @@ -62,8 +72,8 @@ public static boolean valid(final SchemaValidationType validationType, each -> validAttribute(each, fields, validationType)); } - private static boolean validSchema(SchemaValidationType validationType, Set attributes, - List fields) { + private boolean validSchema(SchemaValidationType validationType, Set attributes, + List fields) { final var fieldNames = fields.stream() .map(Field::getName) .map(String::toUpperCase) @@ -98,7 +108,7 @@ public Boolean matching() { }); } - private static List getAllFields(Class type) { + private List getAllFields(Class type) { List fields = new ArrayList<>(); for (Class c = type; c != null; c = c.getSuperclass()) { fields.addAll(Arrays.asList(c.getDeclaredFields())); @@ -106,16 +116,16 @@ private static List getAllFields(Class type) { return fields; } - private static boolean validAttribute(final SchemaAttribute attribute, - List fields, SchemaValidationType validationType) { + private boolean validAttribute(final SchemaAttribute attribute, + List fields, SchemaValidationType validationType) { final var field = fields.stream() .filter(each -> each.getName().equals(attribute.getName())) .findFirst().orElse(null); return null != field && valid(validationType, attribute, field.getGenericType()); } - public static boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final Type type) { + public boolean valid(final SchemaValidationType validationType, + SchemaAttribute attribute, final Type type) { if (type instanceof Class klass) { return valid(validationType, attribute, klass); } else if (type instanceof ParameterizedType parameterizedType) { @@ -127,8 +137,8 @@ public static boolean valid(final SchemaValidationType validationType, } } - private static boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final Class klass) { + private boolean valid(final SchemaValidationType validationType, + SchemaAttribute attribute, final Class klass) { return attribute.accept(new SchemaAttributeHandler<>( assignableCheckFunction.apply(klass)) { @Override @@ -154,8 +164,8 @@ public Boolean accept(ObjectAttribute attribute) { }); } - private static boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final ParameterizedType parameterizedType) { + private boolean valid(final SchemaValidationType validationType, + SchemaAttribute attribute, final ParameterizedType parameterizedType) { return attribute.accept(new SchemaAttributeHandler<>(throwException) { @Override public Boolean accept(ArrayAttribute attribute) { @@ -186,8 +196,8 @@ public Boolean accept(MapAttribute attribute) { }); } - private static boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final GenericArrayType arrayType) { + private boolean valid(final SchemaValidationType validationType, + SchemaAttribute attribute, final GenericArrayType arrayType) { return attribute.accept(new SchemaAttributeHandler<>(throwException) { @Override public Boolean accept(final ArrayAttribute attribute) { @@ -196,7 +206,7 @@ public Boolean accept(final ArrayAttribute attribute) { }); } - private static Type[] getTypeArguments(ParameterizedType parameterizedType) { + private Type[] getTypeArguments(ParameterizedType parameterizedType) { Type[] typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 0) { throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, @@ -205,7 +215,7 @@ private static Type[] getTypeArguments(ParameterizedType parameterizedType) { return typeArguments; } - public static boolean valid(Class klass, SchemaAttribute schemaAttribute) { + public boolean valid(Class klass, SchemaAttribute schemaAttribute) { return schemaAttribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) { }); } diff --git a/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java b/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java index a55c54f..82fe03a 100644 --- a/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java +++ b/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java @@ -23,6 +23,7 @@ import com.grookage.leia.validator.exception.ValidationErrorCode; import com.grookage.leia.validator.utils.SchemaValidationUtils; import lombok.SneakyThrows; +import org.apache.commons.lang3.ClassUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -228,4 +229,12 @@ static class InvalidNestedObjectTestClass { static class GenericArrayTestClass { List[] arrayAttribute; } + + @Test + void testIsAssignableFrom() { + Class primitive = long.class; + final var dataType = DataType.LONG; + Assertions.assertTrue(primitive.isPrimitive()); + Assertions.assertTrue(ClassUtils.isAssignable(primitive, dataType.getAssignableClass())); + } } From 41ed9fadc536bf32bc0f7124877230ed4b83995a Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Thu, 28 Nov 2024 12:30:09 +0530 Subject: [PATCH 02/12] version bump --- CHANGELOG.md | 5 +++++ README.md | 2 +- leia-bom/pom.xml | 2 +- leia-client-dropwizard/pom.xml | 2 +- leia-client/pom.xml | 2 +- leia-core/pom.xml | 2 +- leia-dropwizard-es/pom.xml | 2 +- leia-dropwizard/pom.xml | 2 +- leia-elastic/pom.xml | 2 +- leia-models/pom.xml | 2 +- leia-parent/pom.xml | 2 +- leia-refresher/pom.xml | 2 +- leia-repository/pom.xml | 2 +- leia-schema-validator/pom.xml | 2 +- pom.xml | 2 +- 15 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d75bc38..75786f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.0.1-RC7] + +- LeiaClient - SchemaUtil to add support for building SchemaAttributes from the Schema class +- LeiaCore - Added API support to validate schema payload against the SchemaDetails of a specified key + ## [0.0.1-RC6] - LeiaClient - Introduced AuthHeaderSupplier in LeiaClientSupplier to support authentication on leia-server diff --git a/README.md b/README.md index 578c76c..522edf3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Leia is a governance and metadata framework aimed at meeting compliance requirem com.grookage.leia leia-bom - 0.0.1-RC6 + 0.0.1-RC7 ``` diff --git a/leia-bom/pom.xml b/leia-bom/pom.xml index a8f5330..b9f39cb 100644 --- a/leia-bom/pom.xml +++ b/leia-bom/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia - 0.0.1-RC6 + 0.0.1-RC7 leia-bom diff --git a/leia-client-dropwizard/pom.xml b/leia-client-dropwizard/pom.xml index b0e5970..c722706 100644 --- a/leia-client-dropwizard/pom.xml +++ b/leia-client-dropwizard/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-client/pom.xml b/leia-client/pom.xml index d5b7725..f2da970 100644 --- a/leia-client/pom.xml +++ b/leia-client/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-core/pom.xml b/leia-core/pom.xml index 205f66b..f3d67e3 100644 --- a/leia-core/pom.xml +++ b/leia-core/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-dropwizard-es/pom.xml b/leia-dropwizard-es/pom.xml index 1f0d83f..9652139 100644 --- a/leia-dropwizard-es/pom.xml +++ b/leia-dropwizard-es/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-dropwizard/pom.xml b/leia-dropwizard/pom.xml index 5d9fcd8..0bc25d3 100644 --- a/leia-dropwizard/pom.xml +++ b/leia-dropwizard/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-elastic/pom.xml b/leia-elastic/pom.xml index 9cb71e2..4f2152b 100644 --- a/leia-elastic/pom.xml +++ b/leia-elastic/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-models/pom.xml b/leia-models/pom.xml index 6f26400..dea8fa5 100644 --- a/leia-models/pom.xml +++ b/leia-models/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-parent/pom.xml b/leia-parent/pom.xml index d9ea9f5..39fb8f7 100644 --- a/leia-parent/pom.xml +++ b/leia-parent/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia-bom - 0.0.1-RC6 + 0.0.1-RC7 ../leia-bom diff --git a/leia-refresher/pom.xml b/leia-refresher/pom.xml index 9d95f40..30cb48d 100644 --- a/leia-refresher/pom.xml +++ b/leia-refresher/pom.xml @@ -21,7 +21,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-repository/pom.xml b/leia-repository/pom.xml index 50fc7e6..cc330ef 100644 --- a/leia-repository/pom.xml +++ b/leia-repository/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/leia-schema-validator/pom.xml b/leia-schema-validator/pom.xml index d3d5251..9a0a9d5 100644 --- a/leia-schema-validator/pom.xml +++ b/leia-schema-validator/pom.xml @@ -22,7 +22,7 @@ com.grookage.leia leia-parent - 0.0.1-RC6 + 0.0.1-RC7 ../leia-parent diff --git a/pom.xml b/pom.xml index dc78d7f..462922c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 4.0.0 com.grookage.leia leia - 0.0.1-RC6 + 0.0.1-RC7 pom Leia From ae6eeff6f1460cd86bb8ff9c43e489ca9db9c710 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Fri, 29 Nov 2024 11:40:01 +0530 Subject: [PATCH 03/12] moves qualifiers to set on attribute --- .../leia/client/utils/SchemaUtil.java | 115 +++++++++++------ .../leia/client/utils/SchemaUtilTest.java | 120 ++++++++++-------- .../leia/core/utils/ValidationUtils.java | 5 +- .../leia/core/utils/ValidationUtilsTest.java | 52 ++++---- .../leia/models/annotations/Optional.java | 11 ++ .../qualifiers/Encrypted.java} | 9 +- .../models/annotations/qualifiers/PII.java | 11 ++ .../annotations/qualifiers/ShortLived.java | 12 ++ .../annotations/qualifiers/Standard.java | 11 ++ .../models/attributes/ArrayAttribute.java | 6 +- .../models/attributes/BooleanAttribute.java | 6 +- .../leia/models/attributes/ByteAttribute.java | 6 +- .../models/attributes/DoubleAttribute.java | 6 +- .../leia/models/attributes/EnumAttribute.java | 4 +- .../models/attributes/FloatAttribute.java | 6 +- .../models/attributes/IntegerAttribute.java | 6 +- .../leia/models/attributes/LongAttribute.java | 6 +- .../leia/models/attributes/MapAttribute.java | 6 +- .../models/attributes/ObjectAttribute.java | 4 +- .../models/attributes/SchemaAttribute.java | 3 +- .../models/attributes/StringAttribute.java | 6 +- .../leia/models/qualifiers/QualifierInfo.java | 19 --- .../leia/models/attributes/AttributeTest.java | 4 +- 23 files changed, 263 insertions(+), 171 deletions(-) create mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java rename leia-models/src/main/java/com/grookage/leia/models/{qualifiers/annotations/Qualifier.java => annotations/qualifiers/Encrypted.java} (50%) create mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java create mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java create mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java diff --git a/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java b/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java index f30abae..068a683 100644 --- a/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java +++ b/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java @@ -1,5 +1,10 @@ package com.grookage.leia.client.utils; +import com.grookage.leia.models.annotations.Optional; +import com.grookage.leia.models.annotations.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.qualifiers.PII; +import com.grookage.leia.models.annotations.qualifiers.ShortLived; +import com.grookage.leia.models.annotations.qualifiers.Standard; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; import com.grookage.leia.models.attributes.DoubleAttribute; @@ -11,13 +16,14 @@ import com.grookage.leia.models.attributes.ObjectAttribute; import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.qualifiers.QualifierInfo; -import com.grookage.leia.models.qualifiers.annotations.Qualifier; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; +import com.grookage.leia.models.qualifiers.StandardQualifier; import lombok.experimental.UtilityClass; import org.apache.commons.lang3.ClassUtils; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; @@ -25,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,21 +56,21 @@ private SchemaAttribute schemaAttribute(final Field field) { private SchemaAttribute schemaAttribute(final Type type, final String name, - final QualifierInfo qualifierInfo, + final Set qualifiers, final boolean optional) { // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) if (type instanceof Class klass) { - return schemaAttribute(klass, name, qualifierInfo, optional); + return schemaAttribute(klass, name, qualifiers, optional); } // Handle ParameterizedType (e.g., List, Map) if (type instanceof ParameterizedType parameterizedType) { - return schemaAttribute(parameterizedType, name, qualifierInfo, optional); + return schemaAttribute(parameterizedType, name, qualifiers, optional); } // Handle GenericArrayType (e.g., T[], List) if (type instanceof GenericArrayType genericArrayType) { - return schemaAttribute(genericArrayType, name, qualifierInfo, optional); + return schemaAttribute(genericArrayType, name, qualifiers, optional); } throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); @@ -71,52 +78,57 @@ private SchemaAttribute schemaAttribute(final Type type, private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, final String name, - final QualifierInfo qualifierInfo, + final Set qualifiers, final boolean optional) { Class rawType = (Class) parameterizedType.getRawType(); // Handle List or Set if (ClassUtils.isAssignable(rawType, Collection.class)) { - return handleCollection(parameterizedType, name, qualifierInfo, optional); + return handleCollection(parameterizedType, name, qualifiers, optional); } // Handle Map if (ClassUtils.isAssignable(rawType, Map.class)) { - return handleMap(parameterizedType, name, qualifierInfo, optional); + return handleMap(parameterizedType, name, qualifiers, optional); } throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); } - private SchemaAttribute handleMap(ParameterizedType parameterizedType, String name, QualifierInfo qualifierInfo, boolean optional) { + private SchemaAttribute handleMap(ParameterizedType parameterizedType, + String name, + Set qualifiers, + boolean optional) { final var keyType = parameterizedType.getActualTypeArguments()[0]; final var valueType = parameterizedType.getActualTypeArguments()[1]; return new MapAttribute( name, optional, - qualifierInfo, + qualifiers, schemaAttribute(keyType, "key", getQualifierInfo(keyType), isOptional(keyType)), schemaAttribute(valueType, "value", getQualifierInfo(valueType), isOptional(valueType)) ); } - private SchemaAttribute handleCollection(ParameterizedType parameterizedType, String name, QualifierInfo qualifierInfo, boolean optional) { + private SchemaAttribute handleCollection(ParameterizedType parameterizedType, + String name, + Set qualifiers, boolean optional) { final var elementType = parameterizedType.getActualTypeArguments()[0]; return new ArrayAttribute( name, optional, - qualifierInfo, + qualifiers, schemaAttribute(elementType, "element", getQualifierInfo(elementType), isOptional(elementType)) ); } private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, final String name, - final QualifierInfo qualifierInfo, + final Set qualifiers, final boolean optional) { final var componentType = genericArrayType.getGenericComponentType(); return new ArrayAttribute( name, optional, - qualifierInfo, + qualifiers, schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) ); } @@ -124,18 +136,18 @@ private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, private SchemaAttribute schemaAttribute(final Class klass, final String name, - QualifierInfo qualifierInfo, + Set qualifiers, final boolean optional) { if (klass == String.class) { - return new StringAttribute(name, optional, qualifierInfo); + return new StringAttribute(name, optional, qualifiers); } if (klass.isEnum()) { - return new EnumAttribute(name, optional, qualifierInfo, getEnumValues(klass)); + return new EnumAttribute(name, optional, qualifiers, getEnumValues(klass)); } if (klass.isPrimitive()) { - return handlePrimitive(klass, name, qualifierInfo, optional); + return handlePrimitive(klass, name, qualifiers, optional); } // Handle String[], Object[] etc. @@ -144,35 +156,34 @@ private SchemaAttribute schemaAttribute(final Class klass, return new ArrayAttribute( name, optional, - qualifierInfo, + qualifiers, schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) ); } - // Handling custom defined POJO's final var schemaAttributes = buildSchemaAttributes(klass); - return new ObjectAttribute(name, optional, qualifierInfo, schemaAttributes); + return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); } private SchemaAttribute handlePrimitive(final Class klass, final String name, - final QualifierInfo qualifierInfo, + final Set qualifiers, final boolean optional) { if (klass == Integer.class || klass == int.class) { - return new IntegerAttribute(name, optional, qualifierInfo); + return new IntegerAttribute(name, optional, qualifiers); } if (klass == Boolean.class || klass == boolean.class) { - return new BooleanAttribute(name, optional, qualifierInfo); + return new BooleanAttribute(name, optional, qualifiers); } if (klass == Double.class || klass == double.class) { - return new DoubleAttribute(name, optional, qualifierInfo); + return new DoubleAttribute(name, optional, qualifiers); } if (klass == Long.class || klass == long.class) { - return new LongAttribute(name, optional, qualifierInfo); + return new LongAttribute(name, optional, qualifiers); } if (klass == Float.class || klass == float.class) { - return new FloatAttribute(name, optional, qualifierInfo); + return new FloatAttribute(name, optional, qualifiers); } throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); @@ -193,20 +204,47 @@ private Set getEnumValues(Class klass) { .collect(Collectors.toSet()); } - private QualifierInfo getQualifierInfo(Field field) { - Qualifier qualifier = field.getAnnotation(Qualifier.class); - return QualifierInfo.toQualifierInfo(qualifier); + private Set getQualifierInfo(Field field) { + Set qualifierInfos = new HashSet<>(); + if (field.isAnnotationPresent(Encrypted.class)) { + qualifierInfos.add(new EncryptedQualifier()); + } + if (field.isAnnotationPresent(Standard.class)) { + qualifierInfos.add(new StandardQualifier()); + } + if (field.isAnnotationPresent(PII.class)) { + qualifierInfos.add(new PIIQualifier()); + } + if (field.isAnnotationPresent(ShortLived.class)) { + final var shortLived = field.getAnnotation(ShortLived.class); + qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + } + return qualifierInfos; } - private QualifierInfo getQualifierInfo(Type type) { + private Set getQualifierInfo(Type type) { if (type instanceof Class klass) { return getQualifierInfo(klass); } - return null; + return new HashSet<>(); } - private QualifierInfo getQualifierInfo(Class klass) { - return QualifierInfo.toQualifierInfo(klass.getAnnotation(Qualifier.class)); + private Set getQualifierInfo(Class klass) { + Set qualifierInfos = new HashSet<>(); + if (klass.isAnnotationPresent(Encrypted.class)) { + qualifierInfos.add(new EncryptedQualifier()); + } + if (klass.isAnnotationPresent(Standard.class)) { + qualifierInfos.add(new StandardQualifier()); + } + if (klass.isAnnotationPresent(PII.class)) { + qualifierInfos.add(new PIIQualifier()); + } + if (klass.isAnnotationPresent(ShortLived.class)) { + final var shortLived = klass.getAnnotation(ShortLived.class); + qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + } + return qualifierInfos; } private boolean isOptional(Type type) { @@ -217,11 +255,10 @@ private boolean isOptional(Type type) { } private boolean isOptional(Class klass) { - return !klass.isAnnotationPresent(NotNull.class) && !klass.isAnnotationPresent(NotEmpty.class); + return klass.isAnnotationPresent(Optional.class); } private boolean isOptional(Field field) { - // Check for @NotNull and @NotEmpty annotations - return !field.isAnnotationPresent(NotNull.class) && !field.isAnnotationPresent(NotEmpty.class);// Default to true if no such annotations are present + return field.isAnnotationPresent(Optional.class); } } diff --git a/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java b/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java index 334bc59..1e558b5 100644 --- a/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java +++ b/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java @@ -1,5 +1,8 @@ package com.grookage.leia.client.utils; +import com.grookage.leia.models.annotations.Optional; +import com.grookage.leia.models.annotations.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.qualifiers.PII; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; import com.grookage.leia.models.attributes.ByteAttribute; @@ -13,23 +16,17 @@ import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; import com.grookage.leia.models.attributes.StringAttribute; -import com.grookage.leia.models.qualifiers.EncryptedQualifier; -import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.qualifiers.QualifierInfo; import com.grookage.leia.models.qualifiers.QualifierType; import com.grookage.leia.models.qualifiers.ShortLivedQualifier; -import com.grookage.leia.models.qualifiers.StandardQualifier; -import com.grookage.leia.models.qualifiers.annotations.Qualifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import javax.validation.constraints.NotEmpty; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; class SchemaUtilTest { @@ -39,9 +36,9 @@ void testSchemaAttributes_WithPrimitiveClass() { final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestWithPrimitive.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", false, new StandardQualifier()); + final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); } @@ -50,9 +47,9 @@ void testSchemaAttributes_WithRecordClass() { final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestRecord.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", false, new PIIQualifier()); + final var nameAttribute = new StringAttribute("name", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); } @@ -61,34 +58,34 @@ void testSchemaAttributes_WithNestedObject() { final var schemaAttributes = SchemaUtil.buildSchemaAttributes(TestWithNested.class); Assertions.assertFalse(schemaAttributes.isEmpty()); Assertions.assertEquals(6, schemaAttributes.size()); - final var nameAttribute = new StringAttribute("name", true, new StandardQualifier()); + final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); equals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); equals(idAttribute, filter(schemaAttributes, "id").orElse(null)); final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", true, new StandardQualifier()); - final var accountNumberAttribute = new StringAttribute("accountNumber", true, new EncryptedQualifier()); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier())); testPIIDataAttributes.add(piiNameAttribute); testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataAttribute = new ObjectAttribute("piiData", true, new PIIQualifier(), testPIIDataAttributes); + final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), testPIIDataAttributes); equals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); final var testRecordAttributes = new HashSet(); - final var recordNameAttribute = new StringAttribute("name", false, new PIIQualifier()); - final var recordIdAttribute = new IntegerAttribute("id", true, new StandardQualifier()); + final var recordNameAttribute = new StringAttribute("name", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); + final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); testRecordAttributes.add(recordNameAttribute); testRecordAttributes.add(recordIdAttribute); - final var testRecordAttribute = new ObjectAttribute("testRecord", true, new EncryptedQualifier(), + final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier()), testRecordAttributes); equals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); - final var enumClassAttribute = new EnumAttribute("enumClass", true, new StandardQualifier(), Set.of(EnumClass.ONE.name(), + final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())); equals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); - final var phoneNoAttribute = new StringAttribute("phoneNumber", true, new PIIQualifier()); + final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); equals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); } @@ -98,22 +95,22 @@ void testSchemaAttributes_WithParameterizedType() { Assertions.assertNotNull(schemaAttributes); Assertions.assertEquals(3, schemaAttributes.size()); - final var valuesAttributes = new ArrayAttribute("values", true, new StandardQualifier(), - new StringAttribute("element", true, new StandardQualifier())); + final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), + new StringAttribute("element", false, new HashSet<>())); equals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", true, new StandardQualifier()); - final var accountNumberAttribute = new StringAttribute("accountNumber", true, new EncryptedQualifier()); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier())); testPIIDataAttributes.add(piiNameAttribute); testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataListAttribute = new ArrayAttribute("piiDataList", true, new PIIQualifier(), - new ObjectAttribute("element", true, new PIIQualifier(), testPIIDataAttributes)); + final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), + new ObjectAttribute("element", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), testPIIDataAttributes)); equals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); - final var mapAttribute = new MapAttribute("map", true, new EncryptedQualifier(), - new EnumAttribute("key", true, new StandardQualifier(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), - new StringAttribute("value", true, new StandardQualifier())); + final var mapAttribute = new MapAttribute("map", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier()), + new EnumAttribute("key", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), + new StringAttribute("value", false, new HashSet<>())); equals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); } @@ -122,20 +119,20 @@ enum EnumClass { TWO } - @Qualifier(type = QualifierType.PII) + @PII static class TestPIIData { String name; - @Qualifier(type = QualifierType.ENCRYPTED) + @Encrypted String accountNumber; } - static record TestRecord(@Qualifier(type = QualifierType.PII) @NotEmpty String name, - int id) { + record TestRecord(@PII String name, + @Optional int id) { } static class TestWithPrimitive { - @NotEmpty + @Optional String name; int id; } @@ -143,25 +140,25 @@ static class TestWithPrimitive { static class TestWithNested { String name; int id; - @Qualifier(type = QualifierType.PII) + @PII TestPIIData piiData; - @Qualifier(type = QualifierType.ENCRYPTED) + @Encrypted TestRecord testRecord; EnumClass enumClass; - @Qualifier(type = QualifierType.PII) + @PII String phoneNumber; } static class TestWithParameterized { String[] values; - @Qualifier(type = QualifierType.PII) + @PII List piiDataList; - @Qualifier(type = QualifierType.ENCRYPTED) + @Encrypted Map map; } - private Optional filter(Set schemaAttributes, - String name) { + private java.util.Optional filter(Set schemaAttributes, + String name) { return schemaAttributes.stream() .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) .findFirst(); @@ -177,7 +174,7 @@ private void equals(SchemaAttribute expected, Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); // Compare QualifierInfo - equals(expected.getQualifierInfo(), original.getQualifierInfo()); + equals(expected.getQualifiers(), original.getQualifiers()); // Accept the expected attribute type and perform specific validations expected.accept(new SchemaAttributeAcceptor() { @@ -267,15 +264,34 @@ public Void accept(ObjectAttribute attribute) { }); } - private void equals(QualifierInfo expected, - QualifierInfo original) { - if (Objects.isNull(expected) && Objects.isNull(original)) { - return; - } - Assertions.assertEquals(expected.getType(), original.getType()); - if (expected instanceof ShortLivedQualifier expectedQualifier - && original instanceof ShortLivedQualifier originalQualifier) { - Assertions.assertEquals(expectedQualifier.getTtlSeconds(), originalQualifier.getTtlSeconds()); - } + private void equals(Set expected, + Set original) { + Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); + Assertions.assertNotNull(original, "Actual qualifiers should not be null"); + Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); + + expected.forEach(expectedQualifier -> { + java.util.Optional matchingQualifier = filter(original, expectedQualifier.getType()); + + Assertions.assertTrue(matchingQualifier.isPresent(), + "Missing qualifier of type: " + expectedQualifier.getType()); + + if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) { + Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier"); + + ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier; + ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get(); + + Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(), + "Mismatch in TTL seconds for SHORT_LIVED qualifier"); + } + }); + } + + private java.util.Optional filter(Set qualifiers, + QualifierType type) { + return qualifiers.stream() + .filter(qualifierInfo -> qualifierInfo.getType().equals(type)) + .findFirst(); } } \ No newline at end of file diff --git a/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java b/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java index 65324fc..2df169d 100644 --- a/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java +++ b/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java @@ -1,7 +1,6 @@ package com.grookage.leia.core.utils; import com.fasterxml.jackson.databind.JsonNode; -import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; import com.grookage.leia.models.attributes.ByteAttribute; @@ -16,6 +15,7 @@ import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; import com.grookage.leia.models.attributes.StringAttribute; import com.grookage.leia.models.schema.SchemaValidationType; +import com.grookage.leia.models.utils.MapperUtils; import lombok.experimental.UtilityClass; import java.util.ArrayList; @@ -101,9 +101,8 @@ private void validateMapAttribute(JsonNode fieldNode, SchemaValidationType schemaValidationType, List validationErrors) { fieldNode.fields().forEachRemaining(entry -> { - // Validate key JsonNode keyNode = entry.getKey() != null - ? ResourceHelper.getObjectMapper().convertValue(entry.getKey(), JsonNode.class) + ? MapperUtils.mapper().convertValue(entry.getKey(), JsonNode.class) : null; if (!Objects.isNull(keyNode)) { // validate Key diff --git a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java index d9dffb5..061af8e 100644 --- a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java +++ b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java @@ -1,6 +1,5 @@ package com.grookage.leia.core.utils; -import com.fasterxml.jackson.databind.JsonNode; import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; @@ -12,7 +11,6 @@ import com.grookage.leia.models.schema.SchemaValidationType; import org.junit.jupiter.api.Test; -import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -22,7 +20,7 @@ class ValidationUtilsTest { @Test void testValidJsonAgainstSchema() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "name": "John Doe", "age": 30, @@ -30,20 +28,20 @@ void testValidJsonAgainstSchema() throws Exception { } """); - Set schemaAttributes = Set.of( + final var schemaAttributes = Set.of( new StringAttribute("name", false, null), new IntegerAttribute("age", false, null), new BooleanAttribute("isActive", false, null) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @Test void testUnexpectedFieldInJson() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "name": "John Doe", "age": 30, @@ -52,13 +50,13 @@ void testUnexpectedFieldInJson() throws Exception { } """); - Set schemaAttributes = Set.of( + final var schemaAttributes = Set.of( new StringAttribute("name", false, null), new IntegerAttribute("age", false, null), new BooleanAttribute("isActive", false, null) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -67,18 +65,18 @@ void testUnexpectedFieldInJson() throws Exception { @Test void testMissingRequiredField() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "name": "John Doe" } """); - Set schemaAttributes = Set.of( + final var schemaAttributes = Set.of( new StringAttribute("name", false, null), new IntegerAttribute("age", false, null) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -87,19 +85,19 @@ void testMissingRequiredField() throws Exception { @Test void testTypeMismatch() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "name": "John Doe", "age": "thirty" } """); - Set schemaAttributes = Set.of( + final var schemaAttributes = Set.of( new StringAttribute("name", false, null), new IntegerAttribute("age", false, null) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -108,7 +106,7 @@ void testTypeMismatch() throws Exception { @Test void testNestedObjectValidation() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "user": { "id": 1, @@ -117,40 +115,40 @@ void testNestedObjectValidation() throws Exception { } """); - Set nestedAttributes = Set.of( + final Set nestedAttributes = Set.of( new IntegerAttribute("id", false, null), new StringAttribute("username", false, null) ); - Set schemaAttributes = Set.of( + final Set schemaAttributes = Set.of( new ObjectAttribute("user", false, null, nestedAttributes) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @Test void testArrayValidation() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "numbers": [1, 2, 3, 4] } """); - Set schemaAttributes = Set.of( + final Set schemaAttributes = Set.of( new ArrayAttribute("numbers", false, null, new IntegerAttribute("element", false, null)) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @Test void testMapValidation() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "attributes": { "key1": "value1", @@ -159,7 +157,7 @@ void testMapValidation() throws Exception { } """); - Set schemaAttributes = Set.of( + final Set schemaAttributes = Set.of( new MapAttribute( "attributes", false, @@ -169,14 +167,14 @@ void testMapValidation() throws Exception { ) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @Test void testInvalidMapValueType() throws Exception { - JsonNode jsonNode = ResourceHelper.getObjectMapper().readTree(""" + final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" { "attributes": { "key1": 100 @@ -184,7 +182,7 @@ void testInvalidMapValueType() throws Exception { } """); - Set schemaAttributes = Set.of( + final Set schemaAttributes = Set.of( new MapAttribute( "attributes", false, @@ -194,7 +192,7 @@ void testInvalidMapValueType() throws Exception { ) ); - List errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java new file mode 100644 index 0000000..e69f6c9 --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java @@ -0,0 +1,11 @@ +package com.grookage.leia.models.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Optional { +} diff --git a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java similarity index 50% rename from leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java rename to leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java index 4da1fa6..fa929e4 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/annotations/Qualifier.java +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java @@ -1,6 +1,4 @@ -package com.grookage.leia.models.qualifiers.annotations; - -import com.grookage.leia.models.qualifiers.QualifierType; +package com.grookage.leia.models.annotations.qualifiers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -9,8 +7,5 @@ @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) -public @interface Qualifier { - QualifierType type(); - - int ttlSeconds() default -1; // Applicable for ShortLivedQualifier +public @interface Encrypted { } diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java new file mode 100644 index 0000000..c0ba5f6 --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java @@ -0,0 +1,11 @@ +package com.grookage.leia.models.annotations.qualifiers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PII { +} diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java new file mode 100644 index 0000000..1cf1e0f --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java @@ -0,0 +1,12 @@ +package com.grookage.leia.models.annotations.qualifiers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ShortLived { + int ttlSeconds(); +} diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java new file mode 100644 index 0000000..7322ca1 --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java @@ -0,0 +1,11 @@ +package com.grookage.leia.models.annotations.qualifiers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Standard { +} diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java index 3168147..38d22f1 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ArrayAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -31,9 +33,9 @@ public class ArrayAttribute extends SchemaAttribute { public ArrayAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo, + final Set qualifiers, SchemaAttribute elementAttribute) { - super(DataType.ARRAY, name, optional, qualifierInfo); + super(DataType.ARRAY, name, optional, qualifiers); this.elementAttribute = elementAttribute; } diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java index 89aa779..5c255df 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/BooleanAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class BooleanAttribute extends SchemaAttribute { public BooleanAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.BOOLEAN, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.BOOLEAN, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java index 5ecc453..7dba446 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ByteAttribute.java @@ -23,6 +23,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -31,8 +33,8 @@ public class ByteAttribute extends SchemaAttribute { public ByteAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.BYTES, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.BYTES, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java index a454771..55a0ee8 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/DoubleAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class DoubleAttribute extends SchemaAttribute { public DoubleAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.DOUBLE, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.DOUBLE, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java index 591d22a..10df1ef 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/EnumAttribute.java @@ -34,9 +34,9 @@ public class EnumAttribute extends SchemaAttribute { public EnumAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo, + final Set qualifiers, final Set values) { - super(DataType.ENUM, name, optional, qualifierInfo); + super(DataType.ENUM, name, optional, qualifiers); this.values = values; } diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java index 85ed563..9c4c7e3 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/FloatAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class FloatAttribute extends SchemaAttribute { public FloatAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.FLOAT, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.FLOAT, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java index a3be7d8..7549474 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/IntegerAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class IntegerAttribute extends SchemaAttribute { public IntegerAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.INTEGER, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.INTEGER, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java index b1efa0c..fa6d2ef 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/LongAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class LongAttribute extends SchemaAttribute { public LongAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.LONG, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.LONG, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java index 31b771f..6f4e8b0 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/MapAttribute.java @@ -23,6 +23,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -34,10 +36,10 @@ public class MapAttribute extends SchemaAttribute { public MapAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo, + final Set qualifiers, SchemaAttribute keyAttribute, SchemaAttribute valueAttribute) { - super(DataType.MAP, name, optional, qualifierInfo); + super(DataType.MAP, name, optional, qualifiers); Preconditions.checkArgument(keyAttribute == null || valueAttribute != null); this.keyAttribute = keyAttribute; this.valueAttribute = valueAttribute; diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java index 49892bd..5362346 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/ObjectAttribute.java @@ -33,9 +33,9 @@ public class ObjectAttribute extends SchemaAttribute { public ObjectAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo, + final Set qualifiers, Set nestedAttributes) { - super(DataType.OBJECT, name, optional, qualifierInfo); + super(DataType.OBJECT, name, optional, qualifiers); this.nestedAttributes = nestedAttributes; } diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java index 5121651..e210865 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/SchemaAttribute.java @@ -25,6 +25,7 @@ import lombok.NoArgsConstructor; import javax.validation.constraints.Pattern; +import java.util.Set; @Data @NoArgsConstructor @@ -53,7 +54,7 @@ public abstract class SchemaAttribute { private boolean optional; - private QualifierInfo qualifierInfo; + private Set qualifiers; public abstract T accept(SchemaAttributeAcceptor attributeAcceptor); } diff --git a/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java b/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java index ee768ac..567b7d5 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java +++ b/leia-models/src/main/java/com/grookage/leia/models/attributes/StringAttribute.java @@ -22,6 +22,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import java.util.Set; + @EqualsAndHashCode(callSuper = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @@ -30,8 +32,8 @@ public class StringAttribute extends SchemaAttribute { public StringAttribute(final String name, final boolean optional, - final QualifierInfo qualifierInfo) { - super(DataType.STRING, name, optional, qualifierInfo); + final Set qualifiers) { + super(DataType.STRING, name, optional, qualifiers); } @Override diff --git a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java index f68fcee..a4aff87 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java +++ b/leia-models/src/main/java/com/grookage/leia/models/qualifiers/QualifierInfo.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.grookage.leia.models.qualifiers.annotations.Qualifier; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -39,22 +38,4 @@ public abstract class QualifierInfo { private QualifierType type; - public static QualifierInfo toQualifierInfo(Qualifier qualifier) { - if (qualifier == null) { - return new StandardQualifier(); - } - - final var type = qualifier.type(); - final var ttlInSecs = qualifier.ttlSeconds(); - if (type == QualifierType.SHORT_LIVED) { - return new ShortLivedQualifier(ttlInSecs); - } else if (type == QualifierType.PII) { - return new PIIQualifier(); - } else if (type == QualifierType.ENCRYPTED) { - return new EncryptedQualifier(); - } else { - return new StandardQualifier(); - } - } - } diff --git a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java index 7d12b02..1a64f46 100644 --- a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java +++ b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java @@ -40,6 +40,8 @@ void testAttributeStructures() { Assertions.assertNotNull(attribute); Assertions.assertEquals("testAttribute", attribute.getName()); Assertions.assertSame(DataType.ARRAY, attribute.getType()); - Assertions.assertTrue(attribute.getQualifierInfo() != null && attribute.getQualifierInfo().getType() == QualifierType.PII); + Assertions.assertEquals(1, attribute.getQualifiers().size()); + Assertions.assertEquals(QualifierType.PII, attribute.getQualifiers().stream().findFirst().get().getType()); + } } From 5da6e951279b97c826f8b821d179fe89a63dc8a6 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Fri, 29 Nov 2024 17:44:51 +0530 Subject: [PATCH 04/12] adds leia-common module and moved all validation utils --- README.md | 16 ++-- leia-bom/pom.xml | 5 + leia-client/pom.xml | 6 -- leia-common/pom.xml | 92 ++++++++++++++++++ .../leia/common/builder/SchemaBuilder.java | 96 ++++--------------- .../exception/SchemaValidationException.java | 2 +- .../exception/ValidationErrorCode.java | 2 +- .../leia/common/utils/QualifierUtils.java | 55 +++++++++++ .../com/grookage/leia/common/utils/Utils.java | 56 +++++++++++ .../validation}/SchemaValidationUtils.java | 38 ++------ .../common/builder/SchemaBuilderTest.java | 81 ++++++++-------- .../SchemaValidationUtilsTest.java | 38 ++++---- .../src/test/resources/validNestedSchema.json | 24 +++-- .../src/test/resources/validSchema.json | 16 ++-- .../leia/core/utils/ValidationUtilsTest.java | 48 ++++++++++ .../annotations/qualifiers/Standard.java | 11 --- .../leia/models/attributes/AttributeTest.java | 17 ++++ .../attributeWithMultipleQualifiers.json | 14 +++ .../attributes/attributeWithQualifier.json | 8 +- .../resources/attributes/enumAttribute.json | 8 +- .../resources/schema/createSchemaRequest.json | 8 +- .../test/resources/schema/schemaDetails.json | 19 ++-- .../resources/schema/updateSchemaRequest.json | 16 ++-- leia-schema-validator/pom.xml | 4 + .../leia/validator/StaticSchemaValidator.java | 6 +- pom.xml | 1 + 26 files changed, 454 insertions(+), 233 deletions(-) create mode 100644 leia-common/pom.xml rename leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java => leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java (67%) rename {leia-schema-validator/src/main/java/com/grookage/leia/validator => leia-common/src/main/java/com/grookage/leia/common}/exception/SchemaValidationException.java (98%) rename {leia-schema-validator/src/main/java/com/grookage/leia/validator => leia-common/src/main/java/com/grookage/leia/common}/exception/ValidationErrorCode.java (94%) create mode 100644 leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java create mode 100644 leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java rename {leia-schema-validator/src/main/java/com/grookage/leia/validator/utils => leia-common/src/main/java/com/grookage/leia/common/validation}/SchemaValidationUtils.java (85%) rename leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java => leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java (78%) rename {leia-schema-validator/src/test/java/com/grookage/leia/validator => leia-common/src/test/java/com/grookage/leia/common/validation}/SchemaValidationUtilsTest.java (91%) rename {leia-schema-validator => leia-common}/src/test/resources/validNestedSchema.json (70%) rename {leia-schema-validator => leia-common}/src/test/resources/validSchema.json (78%) delete mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java create mode 100644 leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json diff --git a/README.md b/README.md index 522edf3..0975d14 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,11 @@ A sample schema looks like the following "type": "ARRAY", "name": "testAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] }, { "type": "ENUM", @@ -103,9 +105,11 @@ A sample schema looks like the following "values": [ "TEST_ENUM" ], - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } ], "transformationTargets": [ diff --git a/leia-bom/pom.xml b/leia-bom/pom.xml index b9f39cb..5897097 100644 --- a/leia-bom/pom.xml +++ b/leia-bom/pom.xml @@ -39,6 +39,11 @@ leia-models ${project.version} + + com.grookage.leia + leia-common + ${project.version} + com.grookage.leia leia-core diff --git a/leia-client/pom.xml b/leia-client/pom.xml index f2da970..cfbb232 100644 --- a/leia-client/pom.xml +++ b/leia-client/pom.xml @@ -54,12 +54,6 @@ org.projectlombok - - org.apache.commons - commons-lang3 - ${lang3.version} - - junit-jupiter org.junit.jupiter diff --git a/leia-common/pom.xml b/leia-common/pom.xml new file mode 100644 index 0000000..e88605f --- /dev/null +++ b/leia-common/pom.xml @@ -0,0 +1,92 @@ + + + + + + 4.0.0 + + com.grookage.leia + leia-parent + 0.0.1-RC7 + ../leia-parent + + + leia-common + + + 3.11 + false + + + + + lombok + org.projectlombok + + + + org.apache.commons + commons-lang3 + ${lang3.version} + + + + junit-jupiter + org.junit.jupiter + + + + com.grookage.leia + leia-models + + + + mockito-core + org.mockito + test + + + + mockito-junit-jupiter + org.mockito + test + + + + mockito-inline + org.mockito + test + + + + com.grookage.leia + leia-models + test-jar + + + * + * + + + + + + + + \ No newline at end of file diff --git a/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java similarity index 67% rename from leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java rename to leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java index 068a683..2eae070 100644 --- a/leia-client/src/main/java/com/grookage/leia/client/utils/SchemaUtil.java +++ b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java @@ -1,10 +1,8 @@ -package com.grookage.leia.client.utils; +package com.grookage.leia.common.builder; +import com.grookage.leia.common.utils.QualifierUtils; +import com.grookage.leia.common.utils.Utils; import com.grookage.leia.models.annotations.Optional; -import com.grookage.leia.models.annotations.qualifiers.Encrypted; -import com.grookage.leia.models.annotations.qualifiers.PII; -import com.grookage.leia.models.annotations.qualifiers.ShortLived; -import com.grookage.leia.models.annotations.qualifiers.Standard; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; import com.grookage.leia.models.attributes.DoubleAttribute; @@ -16,11 +14,7 @@ import com.grookage.leia.models.attributes.ObjectAttribute; import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.StringAttribute; -import com.grookage.leia.models.qualifiers.EncryptedQualifier; -import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.qualifiers.QualifierInfo; -import com.grookage.leia.models.qualifiers.ShortLivedQualifier; -import com.grookage.leia.models.qualifiers.StandardQualifier; import lombok.experimental.UtilityClass; import org.apache.commons.lang3.ClassUtils; @@ -28,20 +22,16 @@ import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @UtilityClass -public class SchemaUtil { +public class SchemaBuilder { public Set buildSchemaAttributes(final Class klass) { - return getAllFields(klass) - .stream().map(SchemaUtil::schemaAttribute) + return Utils.getAllFields(klass) + .stream().map(SchemaBuilder::schemaAttribute) .collect(Collectors.toSet()); } @@ -49,7 +39,7 @@ private SchemaAttribute schemaAttribute(final Field field) { return schemaAttribute( field.getGenericType(), field.getName(), - getQualifierInfo(field), + QualifierUtils.getQualifierInfo(field), isOptional(field) ); } @@ -103,8 +93,8 @@ private SchemaAttribute handleMap(ParameterizedType parameterizedType, name, optional, qualifiers, - schemaAttribute(keyType, "key", getQualifierInfo(keyType), isOptional(keyType)), - schemaAttribute(valueType, "value", getQualifierInfo(valueType), isOptional(valueType)) + schemaAttribute(keyType, "key", QualifierUtils.getQualifierInfo(keyType), isOptional(keyType)), + schemaAttribute(valueType, "value", QualifierUtils.getQualifierInfo(valueType), isOptional(valueType)) ); } @@ -116,7 +106,8 @@ private SchemaAttribute handleCollection(ParameterizedType parameterizedType, name, optional, qualifiers, - schemaAttribute(elementType, "element", getQualifierInfo(elementType), isOptional(elementType)) + schemaAttribute(elementType, "element", QualifierUtils.getQualifierInfo(elementType), + isOptional(elementType)) ); } @@ -129,7 +120,8 @@ private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, name, optional, qualifiers, - schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) + schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), + isOptional(componentType)) ); } @@ -143,7 +135,7 @@ private SchemaAttribute schemaAttribute(final Class klass, } if (klass.isEnum()) { - return new EnumAttribute(name, optional, qualifiers, getEnumValues(klass)); + return new EnumAttribute(name, optional, qualifiers, Utils.getEnumValues(klass)); } if (klass.isPrimitive()) { @@ -157,7 +149,8 @@ private SchemaAttribute schemaAttribute(final Class klass, name, optional, qualifiers, - schemaAttribute(componentType, "element", getQualifierInfo(componentType), isOptional(componentType)) + schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), + isOptional(componentType)) ); } @@ -190,63 +183,6 @@ private SchemaAttribute handlePrimitive(final Class klass, } - private List getAllFields(Class type) { - List fields = new ArrayList<>(); - for (Class c = type; c != null; c = c.getSuperclass()) { - fields.addAll(Arrays.asList(c.getDeclaredFields())); - } - return fields; - } - - private Set getEnumValues(Class klass) { - return Arrays.stream(klass.getEnumConstants()) - .map(enumConstant -> ((Enum) enumConstant).name()) - .collect(Collectors.toSet()); - } - - private Set getQualifierInfo(Field field) { - Set qualifierInfos = new HashSet<>(); - if (field.isAnnotationPresent(Encrypted.class)) { - qualifierInfos.add(new EncryptedQualifier()); - } - if (field.isAnnotationPresent(Standard.class)) { - qualifierInfos.add(new StandardQualifier()); - } - if (field.isAnnotationPresent(PII.class)) { - qualifierInfos.add(new PIIQualifier()); - } - if (field.isAnnotationPresent(ShortLived.class)) { - final var shortLived = field.getAnnotation(ShortLived.class); - qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); - } - return qualifierInfos; - } - - private Set getQualifierInfo(Type type) { - if (type instanceof Class klass) { - return getQualifierInfo(klass); - } - return new HashSet<>(); - } - - private Set getQualifierInfo(Class klass) { - Set qualifierInfos = new HashSet<>(); - if (klass.isAnnotationPresent(Encrypted.class)) { - qualifierInfos.add(new EncryptedQualifier()); - } - if (klass.isAnnotationPresent(Standard.class)) { - qualifierInfos.add(new StandardQualifier()); - } - if (klass.isAnnotationPresent(PII.class)) { - qualifierInfos.add(new PIIQualifier()); - } - if (klass.isAnnotationPresent(ShortLived.class)) { - final var shortLived = klass.getAnnotation(ShortLived.class); - qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); - } - return qualifierInfos; - } - private boolean isOptional(Type type) { if (type instanceof Class klass) { return isOptional(klass); diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java b/leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java similarity index 98% rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java rename to leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java index 99667f3..20b8aca 100644 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/SchemaValidationException.java +++ b/leia-common/src/main/java/com/grookage/leia/common/exception/SchemaValidationException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.grookage.leia.validator.exception; +package com.grookage.leia.common.exception; import lombok.Builder; import lombok.Getter; diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java b/leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java similarity index 94% rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java rename to leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java index 03284aa..fff4e56 100644 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/exception/ValidationErrorCode.java +++ b/leia-common/src/main/java/com/grookage/leia/common/exception/ValidationErrorCode.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.grookage.leia.validator.exception; +package com.grookage.leia.common.exception; import lombok.Getter; diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java new file mode 100644 index 0000000..8fea6c4 --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java @@ -0,0 +1,55 @@ +package com.grookage.leia.common.utils; + +import com.grookage.leia.models.annotations.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.qualifiers.PII; +import com.grookage.leia.models.annotations.qualifiers.ShortLived; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; + +@UtilityClass +public class QualifierUtils { + public Set getQualifierInfo(Type type) { + if (type instanceof Class klass) { + return getQualifierInfo(klass); + } + return new HashSet<>(); + } + + public Set getQualifierInfo(Field field) { + Set qualifierInfos = new HashSet<>(); + if (field.isAnnotationPresent(Encrypted.class)) { + qualifierInfos.add(new EncryptedQualifier()); + } + if (field.isAnnotationPresent(PII.class)) { + qualifierInfos.add(new PIIQualifier()); + } + if (field.isAnnotationPresent(ShortLived.class)) { + final var shortLived = field.getAnnotation(ShortLived.class); + qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + } + return qualifierInfos; + } + + public Set getQualifierInfo(Class klass) { + Set qualifierInfos = new HashSet<>(); + if (klass.isAnnotationPresent(Encrypted.class)) { + qualifierInfos.add(new EncryptedQualifier()); + } + if (klass.isAnnotationPresent(PII.class)) { + qualifierInfos.add(new PIIQualifier()); + } + if (klass.isAnnotationPresent(ShortLived.class)) { + final var shortLived = klass.getAnnotation(ShortLived.class); + qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + } + return qualifierInfos; + } +} diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java new file mode 100644 index 0000000..1ed75e6 --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java @@ -0,0 +1,56 @@ +package com.grookage.leia.common.utils; + +import com.grookage.leia.common.exception.SchemaValidationException; +import com.grookage.leia.common.exception.ValidationErrorCode; +import com.grookage.leia.models.annotations.Optional; +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@UtilityClass +public class Utils { + public List getAllFields(Class type) { + List fields = new ArrayList<>(); + for (Class c = type; c != null; c = c.getSuperclass()) { + fields.addAll(Arrays.asList(c.getDeclaredFields())); + } + return fields; + } + + public Type[] getTypeArguments(ParameterizedType parameterizedType) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length == 0) { + throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, + String.format("No type arguments found for %s", parameterizedType)); + } + return typeArguments; + } + + public Set getEnumValues(Class klass) { + return Arrays.stream(klass.getEnumConstants()) + .map(enumConstant -> ((Enum) enumConstant).name()) + .collect(Collectors.toSet()); + } + + private boolean isOptional(Type type) { + if (type instanceof Class klass) { + return isOptional(klass); + } + return false; + } + + private boolean isOptional(Class klass) { + return klass.isAnnotationPresent(Optional.class); + } + + private boolean isOptional(Field field) { + return field.isAnnotationPresent(Optional.class); + } +} diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java similarity index 85% rename from leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java rename to leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java index 5366505..3c54c8c 100644 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/utils/SchemaValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java @@ -14,9 +14,12 @@ * limitations under the License. */ -package com.grookage.leia.validator.utils; +package com.grookage.leia.common.validation; import com.google.common.collect.Sets; +import com.grookage.leia.common.exception.SchemaValidationException; +import com.grookage.leia.common.exception.ValidationErrorCode; +import com.grookage.leia.common.utils.Utils; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.MapAttribute; import com.grookage.leia.models.attributes.ObjectAttribute; @@ -25,8 +28,6 @@ import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaValidationType; import com.grookage.leia.models.schema.SchemaValidationVisitor; -import com.grookage.leia.validator.exception.SchemaValidationException; -import com.grookage.leia.validator.exception.ValidationErrorCode; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ClassUtils; @@ -35,8 +36,6 @@ import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; @@ -64,7 +63,7 @@ public boolean valid(final SchemaDetails schemaDetails, public boolean valid(final SchemaValidationType validationType, Set attributes, final Class klass) { - final var fields = getAllFields(klass); + final var fields = Utils.getAllFields(klass); if (!validSchema(validationType, attributes, fields)) { return false; } @@ -108,14 +107,6 @@ public Boolean matching() { }); } - private List getAllFields(Class type) { - List fields = new ArrayList<>(); - for (Class c = type; c != null; c = c.getSuperclass()) { - fields.addAll(Arrays.asList(c.getDeclaredFields())); - } - return fields; - } - private boolean validAttribute(final SchemaAttribute attribute, List fields, SchemaValidationType validationType) { final var field = fields.stream() @@ -149,12 +140,12 @@ public Boolean accept(ArrayAttribute attribute) { } return valid(validationType, attribute.getElementAttribute(), klass.getComponentType()); } - return Collection.class.isAssignableFrom(klass) && attribute.getElementAttribute() == null; + return ClassUtils.isAssignable(klass, Collection.class) && attribute.getElementAttribute() == null; } @Override public Boolean accept(MapAttribute attribute) { - return Map.class.isAssignableFrom(klass) && attribute.getKeyAttribute() == null; + return ClassUtils.isAssignable(klass, Map.class) && attribute.getKeyAttribute() == null; } @Override @@ -173,10 +164,10 @@ public Boolean accept(ArrayAttribute attribute) { return true; } final var rawType = (Class) parameterizedType.getRawType(); - if (!attribute.getType().getAssignableClass().isAssignableFrom(rawType)) { + if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { return false; } - final var typeArguments = getTypeArguments(parameterizedType); + final var typeArguments = Utils.getTypeArguments(parameterizedType); return valid(validationType, attribute.getElementAttribute(), typeArguments[0]); } @@ -189,7 +180,7 @@ public Boolean accept(MapAttribute attribute) { if (!attribute.getType().getAssignableClass().isAssignableFrom(rawType)) { return false; } - final var typeArguments = getTypeArguments(parameterizedType); + final var typeArguments = Utils.getTypeArguments(parameterizedType); return valid(validationType, attribute.getKeyAttribute(), typeArguments[0]) && valid(validationType, attribute.getValueAttribute(), typeArguments[1]); } @@ -206,15 +197,6 @@ public Boolean accept(final ArrayAttribute attribute) { }); } - private Type[] getTypeArguments(ParameterizedType parameterizedType) { - Type[] typeArguments = parameterizedType.getActualTypeArguments(); - if (typeArguments.length == 0) { - throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, - String.format("No type arguments found for %s", parameterizedType)); - } - return typeArguments; - } - public boolean valid(Class klass, SchemaAttribute schemaAttribute) { return schemaAttribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) { }); diff --git a/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java similarity index 78% rename from leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java rename to leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java index 1e558b5..9c0a567 100644 --- a/leia-client/src/test/java/com/grookage/leia/client/utils/SchemaUtilTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java @@ -1,4 +1,4 @@ -package com.grookage.leia.client.utils; +package com.grookage.leia.common.builder; import com.grookage.leia.models.annotations.Optional; import com.grookage.leia.models.annotations.qualifiers.Encrypted; @@ -16,6 +16,8 @@ import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.qualifiers.QualifierInfo; import com.grookage.leia.models.qualifiers.QualifierType; import com.grookage.leia.models.qualifiers.ShortLivedQualifier; @@ -29,89 +31,89 @@ import java.util.Objects; import java.util.Set; -class SchemaUtilTest { +class SchemaBuilderTest { @Test void testSchemaAttributes_WithPrimitiveClass() { - final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestWithPrimitive.class); + final var schemaAttributeSet = SchemaBuilder.buildSchemaAttributes(TestWithPrimitive.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); - equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); } @Test void testSchemaAttributes_WithRecordClass() { - final var schemaAttributeSet = SchemaUtil.buildSchemaAttributes(TestRecord.class); + final var schemaAttributeSet = SchemaBuilder.buildSchemaAttributes(TestRecord.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); - equals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); + assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); - equals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); } @Test void testSchemaAttributes_WithNestedObject() { - final var schemaAttributes = SchemaUtil.buildSchemaAttributes(TestWithNested.class); + final var schemaAttributes = SchemaBuilder.buildSchemaAttributes(TestWithNested.class); Assertions.assertFalse(schemaAttributes.isEmpty()); Assertions.assertEquals(6, schemaAttributes.size()); final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); - equals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); + assertEquals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - equals(idAttribute, filter(schemaAttributes, "id").orElse(null)); + assertEquals(idAttribute, filter(schemaAttributes, "id").orElse(null)); final var testPIIDataAttributes = new HashSet(); final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier())); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); testPIIDataAttributes.add(piiNameAttribute); testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), testPIIDataAttributes); - equals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); + final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); + assertEquals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); final var testRecordAttributes = new HashSet(); - final var recordNameAttribute = new StringAttribute("name", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); + final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); testRecordAttributes.add(recordNameAttribute); testRecordAttributes.add(recordIdAttribute); - final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier()), + final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new EncryptedQualifier()), testRecordAttributes); - equals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); + assertEquals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())); - equals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); + assertEquals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); - final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier())); - equals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); + final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); + assertEquals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); } @Test void testSchemaAttributes_WithParameterizedType() { - final var schemaAttributes = SchemaUtil.buildSchemaAttributes(TestWithParameterized.class); + final var schemaAttributes = SchemaBuilder.buildSchemaAttributes(TestWithParameterized.class); Assertions.assertNotNull(schemaAttributes); Assertions.assertEquals(3, schemaAttributes.size()); final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), new StringAttribute("element", false, new HashSet<>())); - equals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); + assertEquals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); final var testPIIDataAttributes = new HashSet(); final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier())); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); testPIIDataAttributes.add(piiNameAttribute); testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), - new ObjectAttribute("element", false, Set.of(new com.grookage.leia.models.qualifiers.PIIQualifier()), testPIIDataAttributes)); - equals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); + final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), + new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); + assertEquals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); - final var mapAttribute = new MapAttribute("map", false, Set.of(new com.grookage.leia.models.qualifiers.EncryptedQualifier()), + final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), new EnumAttribute("key", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), new StringAttribute("value", false, new HashSet<>())); - equals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); + assertEquals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); } enum EnumClass { @@ -127,7 +129,7 @@ static class TestPIIData { } record TestRecord(@PII String name, - @Optional int id) { + @Optional int id) { } @@ -141,6 +143,7 @@ static class TestWithNested { String name; int id; @PII + @Encrypted TestPIIData piiData; @Encrypted TestRecord testRecord; @@ -164,8 +167,8 @@ private java.util.Optional filter(Set schemaAt .findFirst(); } - private void equals(SchemaAttribute expected, - SchemaAttribute original) { + private void assertEquals(SchemaAttribute expected, + SchemaAttribute original) { if (Objects.isNull(expected) && Objects.isNull(original)) { return; } @@ -174,7 +177,7 @@ private void equals(SchemaAttribute expected, Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); // Compare QualifierInfo - equals(expected.getQualifiers(), original.getQualifiers()); + assertEquals(expected.getQualifiers(), original.getQualifiers()); // Accept the expected attribute type and perform specific validations expected.accept(new SchemaAttributeAcceptor() { @@ -232,7 +235,7 @@ public Void accept(StringAttribute attribute) { public Void accept(ArrayAttribute attribute) { Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); ArrayAttribute originalArray = (ArrayAttribute) original; - SchemaUtilTest.this.equals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute + SchemaBuilderTest.this.assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute return null; } @@ -240,8 +243,8 @@ public Void accept(ArrayAttribute attribute) { public Void accept(MapAttribute attribute) { Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); MapAttribute originalMap = (MapAttribute) original; - SchemaUtilTest.this.equals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key - SchemaUtilTest.this.equals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value + SchemaBuilderTest.this.assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key + SchemaBuilderTest.this.assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value return null; } @@ -257,15 +260,15 @@ public Void accept(ObjectAttribute attribute) { Iterator originalIterator = originalObject.getNestedAttributes().iterator(); while (expectedIterator.hasNext() && originalIterator.hasNext()) { - SchemaUtilTest.this.equals(expectedIterator.next(), originalIterator.next()); + SchemaBuilderTest.this.assertEquals(expectedIterator.next(), originalIterator.next()); } return null; } }); } - private void equals(Set expected, - Set original) { + private void assertEquals(Set expected, + Set original) { Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); Assertions.assertNotNull(original, "Actual qualifiers should not be null"); Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); diff --git a/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java similarity index 91% rename from leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java rename to leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java index 82fe03a..8784eb8 100644 --- a/leia-schema-validator/src/test/java/com/grookage/leia/validator/SchemaValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java @@ -1,27 +1,21 @@ -/* - * Copyright (c) 2024. Koushik R . - * - * Licensed 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 com.grookage.leia.validator; +package com.grookage.leia.common.validation; +import com.grookage.leia.common.exception.ValidationErrorCode; 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.DataType; +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 com.grookage.leia.validator.exception.ValidationErrorCode; -import com.grookage.leia.validator.utils.SchemaValidationUtils; import lombok.SneakyThrows; import org.apache.commons.lang3.ClassUtils; import org.junit.jupiter.api.Assertions; @@ -33,7 +27,6 @@ import java.util.concurrent.ConcurrentHashMap; class SchemaValidationUtilsTest { - @Test @SneakyThrows void testSchemaValidator() { @@ -237,4 +230,5 @@ void testIsAssignableFrom() { Assertions.assertTrue(primitive.isPrimitive()); Assertions.assertTrue(ClassUtils.isAssignable(primitive, dataType.getAssignableClass())); } -} + +} \ No newline at end of file diff --git a/leia-schema-validator/src/test/resources/validNestedSchema.json b/leia-common/src/test/resources/validNestedSchema.json similarity index 70% rename from leia-schema-validator/src/test/resources/validNestedSchema.json rename to leia-common/src/test/resources/validNestedSchema.json index a424eeb..db00ff5 100644 --- a/leia-schema-validator/src/test/resources/validNestedSchema.json +++ b/leia-common/src/test/resources/validNestedSchema.json @@ -14,25 +14,31 @@ "type": "STRING", "name": "stringAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] }, { "type": "OBJECT", "name": "nestedObjectAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - }, + "qualifiers": [ + { + "type": "PII" + } + ], "nestedAttributes": [ { "type": "INTEGER", "name": "integerAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } ] } diff --git a/leia-schema-validator/src/test/resources/validSchema.json b/leia-common/src/test/resources/validSchema.json similarity index 78% rename from leia-schema-validator/src/test/resources/validSchema.json rename to leia-common/src/test/resources/validSchema.json index 9c06511..a86fbf1 100644 --- a/leia-schema-validator/src/test/resources/validSchema.json +++ b/leia-common/src/test/resources/validSchema.json @@ -17,9 +17,11 @@ "elementAttribute": { "type": "STRING" }, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] }, { "type": "ENUM", @@ -28,9 +30,11 @@ "values": [ "TEST_ENUM" ], - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } ] } \ No newline at end of file diff --git a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java index 061af8e..3177b2b 100644 --- a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java +++ b/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java @@ -9,8 +9,12 @@ import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.StringAttribute; import com.grookage.leia.models.schema.SchemaValidationType; +import lombok.Builder; +import lombok.Data; import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -197,4 +201,48 @@ void testInvalidMapValueType() throws Exception { assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); } + + @Test + void testValidateNested() { + final var testRecord = TestRecord.builder() + .id(100) + .name("name") + .nestedObjectsList(List.of(NestedObject.builder() + .key("key") + .version(5l) + .enumclass(Enumclass.ONE) + .build(), NestedObject.builder() + .key("key") + .version(6l) + .enumclass(Enumclass.TWO) + .build())) + .nestedObjectMap(Map.of(Enumclass.ONE, NestedObject.builder() + .key("key") + .version(7l) + .enumclass(Enumclass.ONE) + .build())) + .build(); + final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(testRecord); + } + + static enum Enumclass { + ONE, + TWO + } + @Data + @Builder + static class NestedObject{ + String key; + long version; + Enumclass enumclass; + } + + @Data + @Builder + static class TestRecord { + String name; + int id; + List nestedObjectsList; + Map nestedObjectMap; + } } \ No newline at end of file diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java deleted file mode 100644 index 7322ca1..0000000 --- a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Standard.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.grookage.leia.models.annotations.qualifiers; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE, ElementType.FIELD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface Standard { -} diff --git a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java index 1a64f46..b85d714 100644 --- a/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java +++ b/leia-models/src/test/java/com/grookage/leia/models/attributes/AttributeTest.java @@ -17,7 +17,9 @@ package com.grookage.leia.models.attributes; import com.grookage.leia.models.ResourceHelper; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; import com.grookage.leia.models.qualifiers.QualifierType; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; import lombok.SneakyThrows; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -42,6 +44,21 @@ void testAttributeStructures() { Assertions.assertSame(DataType.ARRAY, attribute.getType()); Assertions.assertEquals(1, attribute.getQualifiers().size()); Assertions.assertEquals(QualifierType.PII, attribute.getQualifiers().stream().findFirst().get().getType()); + attribute = ResourceHelper.getResource("attributes/attributeWithMultipleQualifiers.json", SchemaAttribute.class); + Assertions.assertNotNull(attribute); + Assertions.assertEquals("testAttribute", attribute.getName()); + Assertions.assertSame(DataType.ARRAY, attribute.getType()); + Assertions.assertEquals(2, attribute.getQualifiers().size()); + final var shortLivedQualifier = (ShortLivedQualifier) attribute.getQualifiers().stream() + .filter(qualifierInfo -> qualifierInfo.getType().equals(QualifierType.SHORT_LIVED)) + .findFirst().orElse(null); + Assertions.assertNotNull(shortLivedQualifier); + Assertions.assertEquals(100, shortLivedQualifier.getTtlSeconds()); + final var encryptedQualifier = (EncryptedQualifier) attribute.getQualifiers().stream() + .filter(qualifierInfo -> qualifierInfo.getType().equals(QualifierType.ENCRYPTED)) + .findFirst() + .orElse(null); + Assertions.assertNotNull(encryptedQualifier); } } diff --git a/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json b/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json new file mode 100644 index 0000000..4bb9cb7 --- /dev/null +++ b/leia-models/src/test/resources/attributes/attributeWithMultipleQualifiers.json @@ -0,0 +1,14 @@ +{ + "type": "ARRAY", + "name": "testAttribute", + "optional": true, + "qualifiers": [ + { + "type": "ENCRYPTED" + }, + { + "type": "SHORT_LIVED", + "ttlSeconds": 100 + } + ] +} \ No newline at end of file diff --git a/leia-models/src/test/resources/attributes/attributeWithQualifier.json b/leia-models/src/test/resources/attributes/attributeWithQualifier.json index 02f792c..e6bbaba 100644 --- a/leia-models/src/test/resources/attributes/attributeWithQualifier.json +++ b/leia-models/src/test/resources/attributes/attributeWithQualifier.json @@ -2,7 +2,9 @@ "type": "ARRAY", "name": "testAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } \ No newline at end of file diff --git a/leia-models/src/test/resources/attributes/enumAttribute.json b/leia-models/src/test/resources/attributes/enumAttribute.json index 2b9e6ce..14cac5b 100644 --- a/leia-models/src/test/resources/attributes/enumAttribute.json +++ b/leia-models/src/test/resources/attributes/enumAttribute.json @@ -5,7 +5,9 @@ "values": [ "TEST_ENUM" ], - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } \ No newline at end of file diff --git a/leia-models/src/test/resources/schema/createSchemaRequest.json b/leia-models/src/test/resources/schema/createSchemaRequest.json index 9b473a1..64bfa43 100644 --- a/leia-models/src/test/resources/schema/createSchemaRequest.json +++ b/leia-models/src/test/resources/schema/createSchemaRequest.json @@ -7,9 +7,11 @@ "type": "ARRAY", "name": "testAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } ] } \ No newline at end of file diff --git a/leia-models/src/test/resources/schema/schemaDetails.json b/leia-models/src/test/resources/schema/schemaDetails.json index 096fb47..af05efd 100644 --- a/leia-models/src/test/resources/schema/schemaDetails.json +++ b/leia-models/src/test/resources/schema/schemaDetails.json @@ -14,9 +14,11 @@ "type": "ARRAY", "name": "testAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] }, { "type": "ENUM", @@ -25,9 +27,14 @@ "values": [ "TEST_ENUM" ], - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + }, + { + "type": "ENCRYPTED" + } + ] } ], "transformationTargets": [ diff --git a/leia-models/src/test/resources/schema/updateSchemaRequest.json b/leia-models/src/test/resources/schema/updateSchemaRequest.json index 570bdd4..d0fd2cf 100644 --- a/leia-models/src/test/resources/schema/updateSchemaRequest.json +++ b/leia-models/src/test/resources/schema/updateSchemaRequest.json @@ -9,9 +9,11 @@ "type": "ARRAY", "name": "testAttribute", "optional": true, - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] }, { "type": "ENUM", @@ -20,9 +22,11 @@ "values": [ "TEST_ENUM" ], - "qualifierInfo": { - "type": "PII" - } + "qualifiers": [ + { + "type": "PII" + } + ] } ] } \ No newline at end of file diff --git a/leia-schema-validator/pom.xml b/leia-schema-validator/pom.xml index 9a0a9d5..5ce41cd 100644 --- a/leia-schema-validator/pom.xml +++ b/leia-schema-validator/pom.xml @@ -41,6 +41,10 @@ com.grookage.leia leia-models + + com.grookage.leia + leia-common + reflections 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 fe2dce8..20b96c6 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 @@ -16,12 +16,12 @@ package com.grookage.leia.validator; +import com.grookage.leia.common.exception.SchemaValidationException; +import com.grookage.leia.common.exception.ValidationErrorCode; +import com.grookage.leia.common.validation.SchemaValidationUtils; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaKey; import com.grookage.leia.validator.annotations.SchemaValidatable; -import com.grookage.leia.validator.exception.SchemaValidationException; -import com.grookage.leia.validator.exception.ValidationErrorCode; -import com.grookage.leia.validator.utils.SchemaValidationUtils; import lombok.Builder; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; diff --git a/pom.xml b/pom.xml index 462922c..52fc0b0 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ leia-parent leia-core leia-models + leia-common leia-dropwizard leia-repository leia-elastic From e5aa303eedf9d2da4657d7f5cf20a33ac7655a13 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Fri, 29 Nov 2024 18:35:30 +0530 Subject: [PATCH 05/12] code refactoring of all utils --- .../leia/common/builder/SchemaBuilder.java | 208 ++---------- .../leia/common/utils/QualifierUtils.java | 19 +- .../common/utils/SchemaAttributeUtils.java | 199 ++++++++++++ .../com/grookage/leia/common/utils/Utils.java | 18 +- .../leia/common}/utils/ValidationUtils.java | 6 +- .../validation/SchemaValidationUtils.java | 28 +- .../common/builder/SchemaBuilderTest.java | 295 ------------------ .../utils/SchemaAttributeUtilsTest.java | 293 +++++++++++++++++ .../common}/utils/ValidationUtilsTest.java | 3 +- leia-core/pom.xml | 5 + .../bundle/resources/SchemaResource.java | 2 +- .../models/annotations/SchemaDefinition.java | 27 ++ .../annotations/{ => attribute}/Optional.java | 2 +- .../{ => attribute}/qualifiers/Encrypted.java | 2 +- .../{ => attribute}/qualifiers/PII.java | 2 +- .../qualifiers/ShortLived.java | 4 +- .../leia/validator/StaticSchemaValidator.java | 10 +- .../annotations/SchemaValidatable.java | 34 -- 18 files changed, 593 insertions(+), 564 deletions(-) create mode 100644 leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java rename {leia-core/src/main/java/com/grookage/leia/core => leia-common/src/main/java/com/grookage/leia/common}/utils/ValidationUtils.java (97%) create mode 100644 leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java rename {leia-core/src/test/java/com/grookage/leia/core => leia-common/src/test/java/com/grookage/leia/common}/utils/ValidationUtilsTest.java (99%) create mode 100644 leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java rename leia-models/src/main/java/com/grookage/leia/models/annotations/{ => attribute}/Optional.java (83%) rename leia-models/src/main/java/com/grookage/leia/models/annotations/{ => attribute}/qualifiers/Encrypted.java (80%) rename leia-models/src/main/java/com/grookage/leia/models/annotations/{ => attribute}/qualifiers/PII.java (80%) rename leia-models/src/main/java/com/grookage/leia/models/annotations/{ => attribute}/qualifiers/ShortLived.java (75%) delete mode 100644 leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java diff --git a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java index 2eae070..084832c 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java +++ b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java @@ -1,200 +1,28 @@ package com.grookage.leia.common.builder; -import com.grookage.leia.common.utils.QualifierUtils; -import com.grookage.leia.common.utils.Utils; -import com.grookage.leia.models.annotations.Optional; -import com.grookage.leia.models.attributes.ArrayAttribute; -import com.grookage.leia.models.attributes.BooleanAttribute; -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.SchemaAttribute; -import com.grookage.leia.models.attributes.StringAttribute; -import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.common.utils.SchemaAttributeUtils; +import com.grookage.leia.models.annotations.SchemaDefinition; +import com.grookage.leia.models.schema.ingestion.CreateSchemaRequest; import lombok.experimental.UtilityClass; -import org.apache.commons.lang3.ClassUtils; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Objects; +import java.util.Optional; @UtilityClass public class SchemaBuilder { - public Set buildSchemaAttributes(final Class klass) { - return Utils.getAllFields(klass) - .stream().map(SchemaBuilder::schemaAttribute) - .collect(Collectors.toSet()); - } - - private SchemaAttribute schemaAttribute(final Field field) { - return schemaAttribute( - field.getGenericType(), - field.getName(), - QualifierUtils.getQualifierInfo(field), - isOptional(field) - ); - } - - private SchemaAttribute schemaAttribute(final Type type, - final String name, - final Set qualifiers, - final boolean optional) { - // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) - if (type instanceof Class klass) { - return schemaAttribute(klass, name, qualifiers, optional); - } - - // Handle ParameterizedType (e.g., List, Map) - if (type instanceof ParameterizedType parameterizedType) { - return schemaAttribute(parameterizedType, name, qualifiers, optional); - } - - // Handle GenericArrayType (e.g., T[], List) - if (type instanceof GenericArrayType genericArrayType) { - return schemaAttribute(genericArrayType, name, qualifiers, optional); - } - - throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); - } - - private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, - final String name, - final Set qualifiers, - final boolean optional) { - Class rawType = (Class) parameterizedType.getRawType(); - // Handle List or Set - if (ClassUtils.isAssignable(rawType, Collection.class)) { - return handleCollection(parameterizedType, name, qualifiers, optional); - } - - // Handle Map - if (ClassUtils.isAssignable(rawType, Map.class)) { - return handleMap(parameterizedType, name, qualifiers, optional); - } - throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); - } - - private SchemaAttribute handleMap(ParameterizedType parameterizedType, - String name, - Set qualifiers, - boolean optional) { - final var keyType = parameterizedType.getActualTypeArguments()[0]; - final var valueType = parameterizedType.getActualTypeArguments()[1]; - return new MapAttribute( - name, - optional, - qualifiers, - schemaAttribute(keyType, "key", QualifierUtils.getQualifierInfo(keyType), isOptional(keyType)), - schemaAttribute(valueType, "value", QualifierUtils.getQualifierInfo(valueType), isOptional(valueType)) + public Optional buildSchemaDetails(final Class klass) { + if (Objects.isNull(klass) || !klass.isAnnotationPresent(SchemaDefinition.class)) { + return Optional.empty(); + } + final var schemaDefinition = klass.getAnnotation(SchemaDefinition.class); + return Optional.of(CreateSchemaRequest.builder() + .schemaName(schemaDefinition.name()) + .namespace(schemaDefinition.namespace()) + .description(schemaDefinition.description()) + .schemaType(schemaDefinition.type()) + .validationType(schemaDefinition.validation()) + .attributes(SchemaAttributeUtils.getSchemaAttributes(klass)) + .build() ); } - - private SchemaAttribute handleCollection(ParameterizedType parameterizedType, - String name, - Set qualifiers, boolean optional) { - final var elementType = parameterizedType.getActualTypeArguments()[0]; - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(elementType, "element", QualifierUtils.getQualifierInfo(elementType), - isOptional(elementType)) - ); - } - - private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, - final String name, - final Set qualifiers, - final boolean optional) { - final var componentType = genericArrayType.getGenericComponentType(); - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), - isOptional(componentType)) - ); - } - - - private SchemaAttribute schemaAttribute(final Class klass, - final String name, - Set qualifiers, - final boolean optional) { - if (klass == String.class) { - return new StringAttribute(name, optional, qualifiers); - } - - if (klass.isEnum()) { - return new EnumAttribute(name, optional, qualifiers, Utils.getEnumValues(klass)); - } - - if (klass.isPrimitive()) { - return handlePrimitive(klass, name, qualifiers, optional); - } - - // Handle String[], Object[] etc. - if (klass.isArray()) { - final var componentType = klass.getComponentType(); - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), - isOptional(componentType)) - ); - } - - // Handling custom defined POJO's - final var schemaAttributes = buildSchemaAttributes(klass); - return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); - } - - private SchemaAttribute handlePrimitive(final Class klass, - final String name, - final Set qualifiers, - final boolean optional) { - if (klass == Integer.class || klass == int.class) { - return new IntegerAttribute(name, optional, qualifiers); - } - if (klass == Boolean.class || klass == boolean.class) { - return new BooleanAttribute(name, optional, qualifiers); - } - if (klass == Double.class || klass == double.class) { - return new DoubleAttribute(name, optional, qualifiers); - } - if (klass == Long.class || klass == long.class) { - return new LongAttribute(name, optional, qualifiers); - } - if (klass == Float.class || klass == float.class) { - return new FloatAttribute(name, optional, qualifiers); - } - - throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); - - } - - private boolean isOptional(Type type) { - if (type instanceof Class klass) { - return isOptional(klass); - } - return false; - } - - private boolean isOptional(Class klass) { - return klass.isAnnotationPresent(Optional.class); - } - - private boolean isOptional(Field field) { - return field.isAnnotationPresent(Optional.class); - } } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java index 8fea6c4..f0239fb 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java @@ -1,21 +1,34 @@ package com.grookage.leia.common.utils; -import com.grookage.leia.models.annotations.qualifiers.Encrypted; -import com.grookage.leia.models.annotations.qualifiers.PII; -import com.grookage.leia.models.annotations.qualifiers.ShortLived; +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import com.grookage.leia.models.annotations.attribute.qualifiers.ShortLived; import com.grookage.leia.models.qualifiers.EncryptedQualifier; import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.QualifierType; import com.grookage.leia.models.qualifiers.ShortLivedQualifier; import lombok.experimental.UtilityClass; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; import java.util.Set; @UtilityClass public class QualifierUtils { + public Optional filter(final Set qualifiers, + final QualifierType type) { + if (Objects.isNull(qualifiers)) { + return Optional.empty(); + } + return qualifiers.stream() + .filter(qualifierInfo -> qualifierInfo.getType().equals(type)) + .findFirst(); + } + public Set getQualifierInfo(Type type) { if (type instanceof Class klass) { return getQualifierInfo(klass); diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java new file mode 100644 index 0000000..7217c66 --- /dev/null +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java @@ -0,0 +1,199 @@ +package com.grookage.leia.common.utils; + +import com.grookage.leia.models.annotations.attribute.Optional; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.BooleanAttribute; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.ClassUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@UtilityClass +public class SchemaAttributeUtils { + public Set getSchemaAttributes(final Class klass) { + return Utils.getAllFields(klass) + .stream() + .map(SchemaAttributeUtils::schemaAttribute) + .collect(Collectors.toSet()); + } + + private SchemaAttribute schemaAttribute(final Field field) { + return schemaAttribute( + field.getGenericType(), + field.getName(), + QualifierUtils.getQualifierInfo(field), + isOptional(field) + ); + } + + private SchemaAttribute schemaAttribute(final Type type, + final String name, + final Set qualifiers, + final boolean optional) { + // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) + if (type instanceof Class klass) { + return schemaAttribute(klass, name, qualifiers, optional); + } + + // Handle ParameterizedType (e.g., List, Map) + if (type instanceof ParameterizedType parameterizedType) { + return schemaAttribute(parameterizedType, name, qualifiers, optional); + } + + // Handle GenericArrayType (e.g., T[], List) + if (type instanceof GenericArrayType genericArrayType) { + return schemaAttribute(genericArrayType, name, qualifiers, optional); + } + + throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); + } + + private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, + final boolean optional) { + final var rawType = (Class) parameterizedType.getRawType(); + // Handle List or Set + if (ClassUtils.isAssignable(rawType, Collection.class)) { + return handleCollection(parameterizedType, name, qualifiers, optional); + } + + // Handle Map + if (ClassUtils.isAssignable(rawType, Map.class)) { + return handleMap(parameterizedType, name, qualifiers, optional); + } + throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); + } + + private SchemaAttribute handleMap(ParameterizedType parameterizedType, + String name, + Set qualifiers, + boolean optional) { + final var keyType = parameterizedType.getActualTypeArguments()[0]; + final var valueType = parameterizedType.getActualTypeArguments()[1]; + return new MapAttribute( + name, + optional, + qualifiers, + schemaAttribute(keyType, "key", QualifierUtils.getQualifierInfo(keyType), isOptional(keyType)), + schemaAttribute(valueType, "value", QualifierUtils.getQualifierInfo(valueType), isOptional(valueType)) + ); + } + + private SchemaAttribute handleCollection(ParameterizedType parameterizedType, + String name, + Set qualifiers, boolean optional) { + final var elementType = parameterizedType.getActualTypeArguments()[0]; + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(elementType, "element", QualifierUtils.getQualifierInfo(elementType), + isOptional(elementType)) + ); + } + + private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, + final String name, + final Set qualifiers, + final boolean optional) { + final var componentType = genericArrayType.getGenericComponentType(); + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), + isOptional(componentType)) + ); + } + + + private SchemaAttribute schemaAttribute(final Class klass, + final String name, + Set qualifiers, + final boolean optional) { + if (klass == String.class) { + return new StringAttribute(name, optional, qualifiers); + } + + if (klass.isEnum()) { + return new EnumAttribute(name, optional, qualifiers, Utils.getEnumValues(klass)); + } + + if (klass.isPrimitive()) { + return handlePrimitive(klass, name, qualifiers, optional); + } + + // Handle String[], Object[] etc. + if (klass.isArray()) { + final var componentType = klass.getComponentType(); + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), + isOptional(componentType)) + ); + } + + // Handling custom defined POJO's + final var schemaAttributes = getSchemaAttributes(klass); + return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); + } + + private SchemaAttribute handlePrimitive(final Class klass, + final String name, + final Set qualifiers, + final boolean optional) { + if (klass == Integer.class || klass == int.class) { + return new IntegerAttribute(name, optional, qualifiers); + } + if (klass == Boolean.class || klass == boolean.class) { + return new BooleanAttribute(name, optional, qualifiers); + } + if (klass == Double.class || klass == double.class) { + return new DoubleAttribute(name, optional, qualifiers); + } + if (klass == Long.class || klass == long.class) { + return new LongAttribute(name, optional, qualifiers); + } + if (klass == Float.class || klass == float.class) { + return new FloatAttribute(name, optional, qualifiers); + } + + throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); + + } + + private boolean isOptional(Type type) { + if (type instanceof Class klass) { + return isOptional(klass); + } + return false; + } + + private boolean isOptional(Class klass) { + return klass.isAnnotationPresent(Optional.class); + } + + private boolean isOptional(Field field) { + return field.isAnnotationPresent(Optional.class); + } +} diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java index 1ed75e6..1a13c83 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java @@ -2,7 +2,6 @@ import com.grookage.leia.common.exception.SchemaValidationException; import com.grookage.leia.common.exception.ValidationErrorCode; -import com.grookage.leia.models.annotations.Optional; import lombok.experimental.UtilityClass; import java.lang.reflect.Field; @@ -25,7 +24,7 @@ public List getAllFields(Class type) { } public Type[] getTypeArguments(ParameterizedType parameterizedType) { - Type[] typeArguments = parameterizedType.getActualTypeArguments(); + final var typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 0) { throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, String.format("No type arguments found for %s", parameterizedType)); @@ -38,19 +37,4 @@ public Set getEnumValues(Class klass) { .map(enumConstant -> ((Enum) enumConstant).name()) .collect(Collectors.toSet()); } - - private boolean isOptional(Type type) { - if (type instanceof Class klass) { - return isOptional(klass); - } - return false; - } - - private boolean isOptional(Class klass) { - return klass.isAnnotationPresent(Optional.class); - } - - private boolean isOptional(Field field) { - return field.isAnnotationPresent(Optional.class); - } } diff --git a/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java similarity index 97% rename from leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java rename to leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java index 2df169d..62124bf 100644 --- a/leia-core/src/main/java/com/grookage/leia/core/utils/ValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java @@ -1,4 +1,4 @@ -package com.grookage.leia.core.utils; +package com.grookage.leia.common.utils; import com.fasterxml.jackson.databind.JsonNode; import com.grookage.leia.models.attributes.ArrayAttribute; @@ -61,7 +61,7 @@ public static List validate(JsonNode jsonNode, validateField(jsonNode, attribute, validationType, validationErrors); continue; } - JsonNode fieldNode = jsonNode.get(fieldName); + final var fieldNode = jsonNode.get(fieldName); validateField(fieldNode, attribute, validationType, validationErrors); } @@ -101,7 +101,7 @@ private void validateMapAttribute(JsonNode fieldNode, SchemaValidationType schemaValidationType, List validationErrors) { fieldNode.fields().forEachRemaining(entry -> { - JsonNode keyNode = entry.getKey() != null + final var keyNode = entry.getKey() != null ? MapperUtils.mapper().convertValue(entry.getKey(), JsonNode.class) : null; if (!Objects.isNull(keyNode)) { diff --git a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java index 3c54c8c..939d153 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java @@ -61,7 +61,8 @@ public boolean valid(final SchemaDetails schemaDetails, } public boolean valid(final SchemaValidationType validationType, - Set attributes, final Class klass) { + final Set attributes, + final Class klass) { final var fields = Utils.getAllFields(klass); if (!validSchema(validationType, attributes, fields)) { @@ -71,8 +72,9 @@ public boolean valid(final SchemaValidationType validationType, each -> validAttribute(each, fields, validationType)); } - private boolean validSchema(SchemaValidationType validationType, Set attributes, - List fields) { + private boolean validSchema(final SchemaValidationType validationType, + final Set attributes, + final List fields) { final var fieldNames = fields.stream() .map(Field::getName) .map(String::toUpperCase) @@ -108,7 +110,8 @@ public Boolean matching() { } private boolean validAttribute(final SchemaAttribute attribute, - List fields, SchemaValidationType validationType) { + final List fields, + final SchemaValidationType validationType) { final var field = fields.stream() .filter(each -> each.getName().equals(attribute.getName())) .findFirst().orElse(null); @@ -116,7 +119,8 @@ private boolean validAttribute(final SchemaAttribute attribute, } public boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final Type type) { + final SchemaAttribute attribute, + final Type type) { if (type instanceof Class klass) { return valid(validationType, attribute, klass); } else if (type instanceof ParameterizedType parameterizedType) { @@ -129,7 +133,8 @@ public boolean valid(final SchemaValidationType validationType, } private boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final Class klass) { + final SchemaAttribute attribute, + final Class klass) { return attribute.accept(new SchemaAttributeHandler<>( assignableCheckFunction.apply(klass)) { @Override @@ -156,7 +161,8 @@ public Boolean accept(ObjectAttribute attribute) { } private boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final ParameterizedType parameterizedType) { + final SchemaAttribute attribute, + final ParameterizedType parameterizedType) { return attribute.accept(new SchemaAttributeHandler<>(throwException) { @Override public Boolean accept(ArrayAttribute attribute) { @@ -177,7 +183,7 @@ public Boolean accept(MapAttribute attribute) { return true; } final var rawType = (Class) parameterizedType.getRawType(); - if (!attribute.getType().getAssignableClass().isAssignableFrom(rawType)) { + if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { return false; } final var typeArguments = Utils.getTypeArguments(parameterizedType); @@ -188,7 +194,8 @@ public Boolean accept(MapAttribute attribute) { } private boolean valid(final SchemaValidationType validationType, - SchemaAttribute attribute, final GenericArrayType arrayType) { + final SchemaAttribute attribute, + final GenericArrayType arrayType) { return attribute.accept(new SchemaAttributeHandler<>(throwException) { @Override public Boolean accept(final ArrayAttribute attribute) { @@ -197,7 +204,8 @@ public Boolean accept(final ArrayAttribute attribute) { }); } - public boolean valid(Class klass, SchemaAttribute schemaAttribute) { + public boolean valid(final Class klass, + final SchemaAttribute schemaAttribute) { return schemaAttribute.accept(new SchemaAttributeHandler<>(assignableCheckFunction.apply(klass)) { }); } diff --git a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java index 9c0a567..7f834b4 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java @@ -1,300 +1,5 @@ package com.grookage.leia.common.builder; -import com.grookage.leia.models.annotations.Optional; -import com.grookage.leia.models.annotations.qualifiers.Encrypted; -import com.grookage.leia.models.annotations.qualifiers.PII; -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.SchemaAttribute; -import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; -import com.grookage.leia.models.attributes.StringAttribute; -import com.grookage.leia.models.qualifiers.EncryptedQualifier; -import com.grookage.leia.models.qualifiers.PIIQualifier; -import com.grookage.leia.models.qualifiers.QualifierInfo; -import com.grookage.leia.models.qualifiers.QualifierType; -import com.grookage.leia.models.qualifiers.ShortLivedQualifier; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - class SchemaBuilderTest { - @Test - void testSchemaAttributes_WithPrimitiveClass() { - final var schemaAttributeSet = SchemaBuilder.buildSchemaAttributes(TestWithPrimitive.class); - Assertions.assertNotNull(schemaAttributeSet); - Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); - assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); - } - - @Test - void testSchemaAttributes_WithRecordClass() { - final var schemaAttributeSet = SchemaBuilder.buildSchemaAttributes(TestRecord.class); - Assertions.assertNotNull(schemaAttributeSet); - Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); - assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); - } - - @Test - void testSchemaAttributes_WithNestedObject() { - final var schemaAttributes = SchemaBuilder.buildSchemaAttributes(TestWithNested.class); - Assertions.assertFalse(schemaAttributes.isEmpty()); - Assertions.assertEquals(6, schemaAttributes.size()); - final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); - assertEquals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); - - final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributes, "id").orElse(null)); - - final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); - testPIIDataAttributes.add(piiNameAttribute); - testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); - assertEquals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); - - final var testRecordAttributes = new HashSet(); - final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); - final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); - testRecordAttributes.add(recordNameAttribute); - testRecordAttributes.add(recordIdAttribute); - final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new EncryptedQualifier()), - testRecordAttributes); - assertEquals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); - - final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), - EnumClass.TWO.name())); - assertEquals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); - - final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); - assertEquals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); - } - - @Test - void testSchemaAttributes_WithParameterizedType() { - final var schemaAttributes = SchemaBuilder.buildSchemaAttributes(TestWithParameterized.class); - Assertions.assertNotNull(schemaAttributes); - Assertions.assertEquals(3, schemaAttributes.size()); - - final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), - new StringAttribute("element", false, new HashSet<>())); - assertEquals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); - - final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); - testPIIDataAttributes.add(piiNameAttribute); - testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), - new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); - assertEquals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); - - final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), - new EnumAttribute("key", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), - new StringAttribute("value", false, new HashSet<>())); - assertEquals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); - } - - enum EnumClass { - ONE, - TWO - } - - @PII - static class TestPIIData { - String name; - @Encrypted - String accountNumber; - } - - record TestRecord(@PII String name, - @Optional int id) { - - } - - static class TestWithPrimitive { - @Optional - String name; - int id; - } - - static class TestWithNested { - String name; - int id; - @PII - @Encrypted - TestPIIData piiData; - @Encrypted - TestRecord testRecord; - EnumClass enumClass; - @PII - String phoneNumber; - } - - static class TestWithParameterized { - String[] values; - @PII - List piiDataList; - @Encrypted - Map map; - } - - private java.util.Optional filter(Set schemaAttributes, - String name) { - return schemaAttributes.stream() - .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) - .findFirst(); - } - - private void assertEquals(SchemaAttribute expected, - SchemaAttribute original) { - if (Objects.isNull(expected) && Objects.isNull(original)) { - return; - } - Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch"); - Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch"); - Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); - - // Compare QualifierInfo - assertEquals(expected.getQualifiers(), original.getQualifiers()); - - // Accept the expected attribute type and perform specific validations - expected.accept(new SchemaAttributeAcceptor() { - @Override - public Void accept(BooleanAttribute attribute) { - Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute"); - return null; - } - - @Override - public Void accept(ByteAttribute attribute) { - Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute"); - return null; - } - - @Override - public Void accept(DoubleAttribute attribute) { - Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute"); - return null; - } - - @Override - public Void accept(EnumAttribute attribute) { - Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute"); - EnumAttribute originalEnum = (EnumAttribute) original; - Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch"); - return null; - } - - @Override - public Void accept(FloatAttribute attribute) { - Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute"); - return null; - } - - @Override - public Void accept(IntegerAttribute attribute) { - Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute"); - return null; - } - - @Override - public Void accept(LongAttribute attribute) { - Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute"); - return null; - } - - @Override - public Void accept(StringAttribute attribute) { - Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute"); - return null; - } - - @Override - public Void accept(ArrayAttribute attribute) { - Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); - ArrayAttribute originalArray = (ArrayAttribute) original; - SchemaBuilderTest.this.assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute - return null; - } - - @Override - public Void accept(MapAttribute attribute) { - Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); - MapAttribute originalMap = (MapAttribute) original; - SchemaBuilderTest.this.assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key - SchemaBuilderTest.this.assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value - return null; - } - - @Override - public Void accept(ObjectAttribute attribute) { - Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute"); - ObjectAttribute originalObject = (ObjectAttribute) original; - - // Recursive comparison of nested attributes - Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(), - "Nested attributes size mismatch"); - Iterator expectedIterator = attribute.getNestedAttributes().iterator(); - Iterator originalIterator = originalObject.getNestedAttributes().iterator(); - - while (expectedIterator.hasNext() && originalIterator.hasNext()) { - SchemaBuilderTest.this.assertEquals(expectedIterator.next(), originalIterator.next()); - } - return null; - } - }); - } - - private void assertEquals(Set expected, - Set original) { - Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); - Assertions.assertNotNull(original, "Actual qualifiers should not be null"); - Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); - - expected.forEach(expectedQualifier -> { - java.util.Optional matchingQualifier = filter(original, expectedQualifier.getType()); - - Assertions.assertTrue(matchingQualifier.isPresent(), - "Missing qualifier of type: " + expectedQualifier.getType()); - - if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) { - Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier"); - - ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier; - ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get(); - - Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(), - "Mismatch in TTL seconds for SHORT_LIVED qualifier"); - } - }); - } - - private java.util.Optional filter(Set qualifiers, - QualifierType type) { - return qualifiers.stream() - .filter(qualifierInfo -> qualifierInfo.getType().equals(type)) - .findFirst(); - } } \ No newline at end of file diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java new file mode 100644 index 0000000..2060218 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java @@ -0,0 +1,293 @@ +package com.grookage.leia.common.utils; + +import com.grookage.leia.models.annotations.attribute.Optional; +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.QualifierType; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +class SchemaAttributeUtilsTest { + + @Test + void testSchemaAttributes_WithPrimitiveClass() { + final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(TestWithPrimitive.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); + assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); + assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithRecordClass() { + final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(TestRecord.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); + assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); + assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithNestedObject() { + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestWithNested.class); + Assertions.assertFalse(schemaAttributes.isEmpty()); + Assertions.assertEquals(6, schemaAttributes.size()); + final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); + assertEquals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); + + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); + assertEquals(idAttribute, filter(schemaAttributes, "id").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); + assertEquals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); + + final var testRecordAttributes = new HashSet(); + final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); + final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); + testRecordAttributes.add(recordNameAttribute); + testRecordAttributes.add(recordIdAttribute); + final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new EncryptedQualifier()), + testRecordAttributes); + assertEquals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); + + final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), + EnumClass.TWO.name())); + assertEquals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); + + final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); + assertEquals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); + } + + @Test + void testSchemaAttributes_WithParameterizedType() { + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestWithParameterized.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(3, schemaAttributes.size()); + + final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), + new StringAttribute("element", false, new HashSet<>())); + assertEquals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), + new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); + assertEquals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); + + final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), + new EnumAttribute("key", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), + new StringAttribute("value", false, new HashSet<>())); + assertEquals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); + } + + enum EnumClass { + ONE, + TWO + } + + @PII + static class TestPIIData { + String name; + @Encrypted + String accountNumber; + } + + record TestRecord(@PII String name, + @Optional int id) { + + } + + static class TestWithPrimitive { + @Optional + String name; + int id; + } + + static class TestWithNested { + String name; + int id; + @PII + @Encrypted + TestPIIData piiData; + @Encrypted + TestRecord testRecord; + EnumClass enumClass; + @PII + String phoneNumber; + } + + static class TestWithParameterized { + String[] values; + @PII + List piiDataList; + @Encrypted + Map map; + } + + private java.util.Optional filter(Set schemaAttributes, + String name) { + return schemaAttributes.stream() + .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) + .findFirst(); + } + + private void assertEquals(SchemaAttribute expected, + SchemaAttribute original) { + if (Objects.isNull(expected) && Objects.isNull(original)) { + return; + } + Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch"); + Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch"); + Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); + + // Compare QualifierInfo + assertEquals(expected.getQualifiers(), original.getQualifiers()); + + // Accept the expected attribute type and perform specific validations + expected.accept(new SchemaAttributeAcceptor() { + @Override + public Void accept(BooleanAttribute attribute) { + Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute"); + return null; + } + + @Override + public Void accept(ByteAttribute attribute) { + Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute"); + return null; + } + + @Override + public Void accept(DoubleAttribute attribute) { + Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute"); + return null; + } + + @Override + public Void accept(EnumAttribute attribute) { + Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute"); + EnumAttribute originalEnum = (EnumAttribute) original; + Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch"); + return null; + } + + @Override + public Void accept(FloatAttribute attribute) { + Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute"); + return null; + } + + @Override + public Void accept(IntegerAttribute attribute) { + Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute"); + return null; + } + + @Override + public Void accept(LongAttribute attribute) { + Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute"); + return null; + } + + @Override + public Void accept(StringAttribute attribute) { + Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute"); + return null; + } + + @Override + public Void accept(ArrayAttribute attribute) { + Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); + ArrayAttribute originalArray = (ArrayAttribute) original; + SchemaAttributeUtilsTest.this.assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute + return null; + } + + @Override + public Void accept(MapAttribute attribute) { + Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); + MapAttribute originalMap = (MapAttribute) original; + SchemaAttributeUtilsTest.this.assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key + SchemaAttributeUtilsTest.this.assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value + return null; + } + + @Override + public Void accept(ObjectAttribute attribute) { + Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute"); + ObjectAttribute originalObject = (ObjectAttribute) original; + + // Recursive comparison of nested attributes + Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(), + "Nested attributes size mismatch"); + Iterator expectedIterator = attribute.getNestedAttributes().iterator(); + Iterator originalIterator = originalObject.getNestedAttributes().iterator(); + + while (expectedIterator.hasNext() && originalIterator.hasNext()) { + SchemaAttributeUtilsTest.this.assertEquals(expectedIterator.next(), originalIterator.next()); + } + return null; + } + }); + } + + private void assertEquals(Set expected, + Set original) { + Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); + Assertions.assertNotNull(original, "Actual qualifiers should not be null"); + Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); + + expected.forEach(expectedQualifier -> { + java.util.Optional matchingQualifier = QualifierUtils.filter(original, expectedQualifier.getType()); + + Assertions.assertTrue(matchingQualifier.isPresent(), + "Missing qualifier of type: " + expectedQualifier.getType()); + + if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) { + Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier"); + + ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier; + ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get(); + + Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(), + "Mismatch in TTL seconds for SHORT_LIVED qualifier"); + } + }); + } +} \ No newline at end of file diff --git a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java similarity index 99% rename from leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java rename to leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java index 3177b2b..321d09e 100644 --- a/leia-core/src/test/java/com/grookage/leia/core/utils/ValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java @@ -1,4 +1,4 @@ -package com.grookage.leia.core.utils; +package com.grookage.leia.common.utils; import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; @@ -222,6 +222,7 @@ void testValidateNested() { .enumclass(Enumclass.ONE) .build())) .build(); + // TODO::Abhishek Finish this final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(testRecord); } diff --git a/leia-core/pom.xml b/leia-core/pom.xml index f3d67e3..72b0d8f 100644 --- a/leia-core/pom.xml +++ b/leia-core/pom.xml @@ -33,6 +33,11 @@ leia-models + + com.grookage.leia + leia-common + + com.grookage.leia leia-repository diff --git a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java index b1209f9..23569c4 100644 --- a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java +++ b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java @@ -19,10 +19,10 @@ import com.codahale.metrics.annotation.ExceptionMetered; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.databind.JsonNode; +import com.grookage.leia.common.utils.ValidationUtils; import com.grookage.leia.core.exception.LeiaErrorCode; import com.grookage.leia.core.exception.LeiaException; import com.grookage.leia.core.retrieval.SchemaRetriever; -import com.grookage.leia.core.utils.ValidationUtils; import com.grookage.leia.models.GenericResponse; import com.grookage.leia.models.request.NamespaceRequest; import com.grookage.leia.models.schema.SchemaDetails; diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java new file mode 100644 index 0000000..beb9b47 --- /dev/null +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/SchemaDefinition.java @@ -0,0 +1,27 @@ +package com.grookage.leia.models.annotations; + +import com.grookage.leia.models.schema.SchemaType; +import com.grookage.leia.models.schema.SchemaValidationType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface SchemaDefinition { + String name(); + + String namespace(); + + String version(); + + String description() default ""; + + SchemaType type() default SchemaType.JSON; + + SchemaValidationType validation() default SchemaValidationType.MATCHING; + + Class[] transformationTargets() default {}; +} diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java similarity index 83% rename from leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java rename to leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java index e69f6c9..c1c92a8 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/annotations/Optional.java +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/Optional.java @@ -1,4 +1,4 @@ -package com.grookage.leia.models.annotations; +package com.grookage.leia.models.annotations.attribute; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java similarity index 80% rename from leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java rename to leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java index fa929e4..68ef32b 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/Encrypted.java +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/Encrypted.java @@ -1,4 +1,4 @@ -package com.grookage.leia.models.annotations.qualifiers; +package com.grookage.leia.models.annotations.attribute.qualifiers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java similarity index 80% rename from leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java rename to leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java index c0ba5f6..5336aa5 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/PII.java +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/PII.java @@ -1,4 +1,4 @@ -package com.grookage.leia.models.annotations.qualifiers; +package com.grookage.leia.models.annotations.attribute.qualifiers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java similarity index 75% rename from leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java rename to leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java index 1cf1e0f..cc5e00d 100644 --- a/leia-models/src/main/java/com/grookage/leia/models/annotations/qualifiers/ShortLived.java +++ b/leia-models/src/main/java/com/grookage/leia/models/annotations/attribute/qualifiers/ShortLived.java @@ -1,4 +1,4 @@ -package com.grookage.leia.models.annotations.qualifiers; +package com.grookage.leia.models.annotations.attribute.qualifiers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -8,5 +8,5 @@ @Target({ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface ShortLived { - int ttlSeconds(); + long ttlSeconds(); } 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 20b96c6..7f1df4d 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,9 +19,9 @@ import com.grookage.leia.common.exception.SchemaValidationException; import com.grookage.leia.common.exception.ValidationErrorCode; import com.grookage.leia.common.validation.SchemaValidationUtils; +import com.grookage.leia.models.annotations.SchemaDefinition; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaKey; -import com.grookage.leia.validator.annotations.SchemaValidatable; import lombok.Builder; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -64,12 +64,12 @@ public void start() { log.info("Starting the schema validator"); packageRoots.forEach(handlerPackage -> { final var reflections = new Reflections(handlerPackage); - final var annotatedClasses = reflections.getTypesAnnotatedWith(SchemaValidatable.class); + final var annotatedClasses = reflections.getTypesAnnotatedWith(SchemaDefinition.class); annotatedClasses.forEach(annotatedClass -> { - final var annotation = annotatedClass.getAnnotation(SchemaValidatable.class); + final var annotation = annotatedClass.getAnnotation(SchemaDefinition.class); final var schemaKey = SchemaKey.builder() - .schemaName(annotation.schemaName()) - .version(annotation.versionId()) + .schemaName(annotation.name()) + .version(annotation.version()) .namespace(annotation.namespace()) .build(); klassRegistry.putIfAbsent(schemaKey, annotatedClass); diff --git a/leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java b/leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java deleted file mode 100644 index fd19e09..0000000 --- a/leia-schema-validator/src/main/java/com/grookage/leia/validator/annotations/SchemaValidatable.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2024. Koushik R . - * - * Licensed 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 com.grookage.leia.validator.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -public @interface SchemaValidatable { - - String schemaName(); - - String versionId(); - - String namespace(); - -} From 8ee39be0bd0e40bc16e7525d68b979e60cd79abd Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Sun, 1 Dec 2024 14:06:00 +0530 Subject: [PATCH 06/12] adds support for plain objects, bug fixes and tests --- .../leia/common/builder/SchemaBuilder.java | 18 +- .../leia/common/utils/QualifierUtils.java | 16 ++ .../common/utils/SchemaAttributeUtils.java | 31 +++ .../com/grookage/leia/common/utils/Utils.java | 16 ++ .../leia/common/utils/ValidationUtils.java | 47 +++- .../validation/SchemaValidationUtils.java | 4 + .../common/builder/SchemaBuilderTest.java | 56 ++++ .../utils/SchemaAttributeUtilsTest.java | 263 +++++------------- .../common/utils/ValidationUtilsTest.java | 77 +++-- .../validation/SchemaValidationUtilsTest.java | 13 +- 10 files changed, 292 insertions(+), 249 deletions(-) diff --git a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java index 084832c..1f8f76f 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java +++ b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024. Koushik R . + * + * Licensed 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 com.grookage.leia.common.builder; import com.grookage.leia.common.utils.SchemaAttributeUtils; @@ -10,7 +26,7 @@ @UtilityClass public class SchemaBuilder { - public Optional buildSchemaDetails(final Class klass) { + public Optional buildSchemaRequest(final Class klass) { if (Objects.isNull(klass) || !klass.isAnnotationPresent(SchemaDefinition.class)) { return Optional.empty(); } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java index f0239fb..4e8ad9f 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024. Koushik R . + * + * Licensed 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 com.grookage.leia.common.utils; import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java index 7217c66..58e4110 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024. Koushik R . + * + * Licensed 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 com.grookage.leia.common.utils; import com.grookage.leia.models.annotations.attribute.Optional; @@ -137,6 +153,7 @@ private SchemaAttribute schemaAttribute(final Class klass, return new EnumAttribute(name, optional, qualifiers, Utils.getEnumValues(klass)); } + // Handle int, long, boolean etc. if (klass.isPrimitive()) { return handlePrimitive(klass, name, qualifiers, optional); } @@ -153,6 +170,20 @@ private SchemaAttribute schemaAttribute(final Class klass, ); } + // Handle Raw List, Set + if (ClassUtils.isAssignable(klass, Collection.class)) { + return new ArrayAttribute(name, optional, qualifiers, null); + } + + // Handle Raw Map + if (ClassUtils.isAssignable(klass, Map.class)) { + return new MapAttribute(name, optional, qualifiers, null, null); + } + + if (klass.equals(Object.class)) { + return new ObjectAttribute(name, optional, qualifiers, null); + } + // Handling custom defined POJO's final var schemaAttributes = getSchemaAttributes(klass); return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java index 1a13c83..11c128e 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024. Koushik R . + * + * Licensed 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 com.grookage.leia.common.utils; import com.grookage.leia.common.exception.SchemaValidationException; diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java index 62124bf..6006e31 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024. Koushik R . + * + * Licensed 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 com.grookage.leia.common.utils; import com.fasterxml.jackson.databind.JsonNode; @@ -82,24 +98,41 @@ private void validateField(JsonNode fieldNode, // Recursively validate nested objects if (attribute instanceof ObjectAttribute objectAttribute) { - validationErrors.addAll(validate(fieldNode, validationType, objectAttribute.getNestedAttributes())); + if (objectAttribute.getNestedAttributes() != null) { + validationErrors.addAll(validate(fieldNode, validationType, objectAttribute.getNestedAttributes())); + } return; } if (attribute instanceof ArrayAttribute arrayAttribute) { - for (JsonNode arrayElement : fieldNode) { - validationErrors.addAll(validate(arrayElement, validationType, Set.of(arrayAttribute.getElementAttribute()))); - } - return; + validateCollectionAttribute(fieldNode, arrayAttribute, validationType, validationErrors); } if (attribute instanceof MapAttribute mapAttribute) { validateMapAttribute(fieldNode, mapAttribute, validationType, validationErrors); } } + private void validateCollectionAttribute(JsonNode fieldNode, + ArrayAttribute arrayAttribute, + SchemaValidationType schemaValidationType, + List validationErrors) { + // Handling Non-Parameterized Collections (eg: List.class, Set.class, Map.class etc.) + if (arrayAttribute.getElementAttribute() == null) { + return; + } + + for (JsonNode arrayElement : fieldNode) { + validateField(arrayElement, arrayAttribute.getElementAttribute(), schemaValidationType, validationErrors); + } + } + private void validateMapAttribute(JsonNode fieldNode, MapAttribute mapAttribute, SchemaValidationType schemaValidationType, List validationErrors) { + // Handling Raw Map.class + if (Objects.isNull(mapAttribute.getKeyAttribute()) && Objects.isNull(mapAttribute.getValueAttribute())) { + return; + } fieldNode.fields().forEachRemaining(entry -> { final var keyNode = entry.getKey() != null ? MapperUtils.mapper().convertValue(entry.getKey(), JsonNode.class) @@ -171,6 +204,10 @@ public Boolean accept(MapAttribute attribute) { @Override public Boolean accept(ObjectAttribute attribute) { + // Handling Object.class + if (attribute.getNestedAttributes() == null) { + return true; + } return fieldNode.isObject(); } }); diff --git a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java index 939d153..dd19b9b 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java @@ -155,6 +155,10 @@ public Boolean accept(MapAttribute attribute) { @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); } }); diff --git a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java index 7f834b4..1314c47 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java @@ -1,5 +1,61 @@ package com.grookage.leia.common.builder; +import com.grookage.leia.common.utils.SchemaAttributeUtils; +import com.grookage.leia.models.annotations.SchemaDefinition; +import com.grookage.leia.models.annotations.attribute.Optional; +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import com.grookage.leia.models.schema.SchemaType; +import com.grookage.leia.models.schema.SchemaValidationType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + class SchemaBuilderTest { + @Test + void testSchemaRequest() { + final var schemaCreateRequest = SchemaBuilder.buildSchemaRequest(TestRecord.class) + .orElse(null); + Assertions.assertNotNull(schemaCreateRequest); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRecord.class); + Assertions.assertEquals(TestRecord.NAME, schemaCreateRequest.getSchemaName()); + Assertions.assertEquals(TestRecord.NAMESPACE, schemaCreateRequest.getNamespace()); + Assertions.assertEquals(TestRecord.DESCRIPTION, schemaCreateRequest.getDescription()); + Assertions.assertEquals(SchemaType.JSON, schemaCreateRequest.getSchemaType()); + Assertions.assertEquals(SchemaValidationType.MATCHING, schemaCreateRequest.getValidationType()); + Assertions.assertEquals(schemaAttributes.size(), schemaCreateRequest.getAttributes().size()); + } + + @Test + void testSchemaRequest_WithInvalidClass() { + Assertions.assertTrue(SchemaBuilder.buildSchemaRequest(null).isEmpty()); + Assertions.assertTrue(SchemaBuilder.buildSchemaRequest(TestObject.class).isEmpty()); + } + + @SchemaDefinition( + name = TestRecord.NAME, + namespace = TestRecord.NAMESPACE, + version = TestRecord.VERSION, + description = TestRecord.DESCRIPTION, + type = SchemaType.JSON, + validation = SchemaValidationType.MATCHING + ) + static class TestRecord { + static final String NAME = "TEST_RECORD"; + static final String NAMESPACE = "test"; + static final String VERSION = "v1"; + static final String DESCRIPTION = "Test Record"; + + int id; + String name; + @PII + @Encrypted + String accountNumber; + long ttl; + @Optional + String accountId; + } + static class TestObject { + String name; + } } \ No newline at end of file diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java index 2060218..401df0c 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java @@ -1,70 +1,62 @@ package com.grookage.leia.common.utils; +import com.grookage.leia.common.TestUtils; +import com.grookage.leia.common.stubs.NestedStub; +import com.grookage.leia.common.stubs.RecordStub; +import com.grookage.leia.common.stubs.TestEnum; +import com.grookage.leia.common.stubs.TestObjectStub; +import com.grookage.leia.common.stubs.TestParameterizedStub; +import com.grookage.leia.common.stubs.TestRawCollectionStub; import com.grookage.leia.models.annotations.attribute.Optional; -import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; -import com.grookage.leia.models.annotations.attribute.qualifiers.PII; 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.SchemaAttribute; -import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; import com.grookage.leia.models.attributes.StringAttribute; import com.grookage.leia.models.qualifiers.EncryptedQualifier; import com.grookage.leia.models.qualifiers.PIIQualifier; -import com.grookage.leia.models.qualifiers.QualifierInfo; -import com.grookage.leia.models.qualifiers.QualifierType; -import com.grookage.leia.models.qualifiers.ShortLivedQualifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Set; class SchemaAttributeUtilsTest { @Test void testSchemaAttributes_WithPrimitiveClass() { - final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(TestWithPrimitive.class); + final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(PrimitiveTestClass.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); - assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributeSet, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributeSet, "id").orElse(null)); } @Test void testSchemaAttributes_WithRecordClass() { - final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(TestRecord.class); + final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(RecordStub.class); Assertions.assertNotNull(schemaAttributeSet); Assertions.assertEquals(2, schemaAttributeSet.size()); final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); - assertEquals(nameAttribute, filter(schemaAttributeSet, "name").orElse(null)); + TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributeSet, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributeSet, "id").orElse(null)); + TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributeSet, "id").orElse(null)); } @Test void testSchemaAttributes_WithNestedObject() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestWithNested.class); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(NestedStub.class); Assertions.assertFalse(schemaAttributes.isEmpty()); Assertions.assertEquals(6, schemaAttributes.size()); final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); - assertEquals(nameAttribute, filter(schemaAttributes, "name").orElse(null)); + TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributes, "name").orElse(null)); final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - assertEquals(idAttribute, filter(schemaAttributes, "id").orElse(null)); + TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributes, "id").orElse(null)); final var testPIIDataAttributes = new HashSet(); final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); @@ -72,34 +64,34 @@ void testSchemaAttributes_WithNestedObject() { testPIIDataAttributes.add(piiNameAttribute); testPIIDataAttributes.add(accountNumberAttribute); final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); - assertEquals(piiDataAttribute, filter(schemaAttributes, "piiData").orElse(null)); + TestUtils.assertEquals(piiDataAttribute, TestUtils.filter(schemaAttributes, "piiData").orElse(null)); final var testRecordAttributes = new HashSet(); final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); testRecordAttributes.add(recordNameAttribute); testRecordAttributes.add(recordIdAttribute); - final var testRecordAttribute = new ObjectAttribute("testRecord", false, Set.of(new EncryptedQualifier()), + final var testRecordAttribute = new ObjectAttribute("recordStub", false, Set.of(new EncryptedQualifier()), testRecordAttributes); - assertEquals(testRecordAttribute, filter(schemaAttributes, "testRecord").orElse(null)); + TestUtils.assertEquals(testRecordAttribute, TestUtils.filter(schemaAttributes, "recordStub").orElse(null)); - final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), - EnumClass.TWO.name())); - assertEquals(enumClassAttribute, filter(schemaAttributes, "enumClass").orElse(null)); + final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), + TestEnum.TWO.name())); + TestUtils.assertEquals(enumClassAttribute, TestUtils.filter(schemaAttributes, "enumClass").orElse(null)); final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); - assertEquals(phoneNoAttribute, filter(schemaAttributes, "phoneNumber").orElse(null)); + TestUtils.assertEquals(phoneNoAttribute, TestUtils.filter(schemaAttributes, "phoneNumber").orElse(null)); } @Test void testSchemaAttributes_WithParameterizedType() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestWithParameterized.class); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestParameterizedStub.class); Assertions.assertNotNull(schemaAttributes); Assertions.assertEquals(3, schemaAttributes.size()); final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), new StringAttribute("element", false, new HashSet<>())); - assertEquals(valuesAttributes, filter(schemaAttributes, "values").orElse(null)); + TestUtils.assertEquals(valuesAttributes, TestUtils.filter(schemaAttributes, "values").orElse(null)); final var testPIIDataAttributes = new HashSet(); final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); @@ -108,186 +100,71 @@ void testSchemaAttributes_WithParameterizedType() { testPIIDataAttributes.add(accountNumberAttribute); final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); - assertEquals(piiDataListAttribute, filter(schemaAttributes, "piiDataList").orElse(null)); + TestUtils.assertEquals(piiDataListAttribute, TestUtils.filter(schemaAttributes, "piiDataList").orElse(null)); final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), - new EnumAttribute("key", false, new HashSet<>(), Set.of(EnumClass.ONE.name(), EnumClass.TWO.name())), + new EnumAttribute("key", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), TestEnum.TWO.name())), new StringAttribute("value", false, new HashSet<>())); - assertEquals(mapAttribute, filter(schemaAttributes, "map").orElse(null)); + TestUtils.assertEquals(mapAttribute, TestUtils.filter(schemaAttributes, "map").orElse(null)); } - enum EnumClass { - ONE, - TWO - } - - @PII - static class TestPIIData { - String name; - @Encrypted - String accountNumber; - } + @Test + void testSchemaAttributes_WithRawCollections() { + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRawCollectionStub.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(6, schemaAttributes.size()); - record TestRecord(@PII String name, - @Optional int id) { + final var rawListAttribute = new ArrayAttribute("rawList", false, Set.of(), null); + TestUtils.assertEquals(rawListAttribute, TestUtils.filter(schemaAttributes, "rawList").orElse(null)); - } + final var rawLinkedListAttribute = new ArrayAttribute("rawLinkedList", false, Set.of(), null); + TestUtils.assertEquals(rawLinkedListAttribute, TestUtils.filter(schemaAttributes, "rawLinkedList").orElse(null)); - static class TestWithPrimitive { - @Optional - String name; - int id; - } + final var rawSetAttribute = new ArrayAttribute("rawSet", false, Set.of(), null); + TestUtils.assertEquals(rawSetAttribute, TestUtils.filter(schemaAttributes, "rawSet").orElse(null)); - static class TestWithNested { - String name; - int id; - @PII - @Encrypted - TestPIIData piiData; - @Encrypted - TestRecord testRecord; - EnumClass enumClass; - @PII - String phoneNumber; - } + final var rawHashSetAttribute = new ArrayAttribute("rawHashSet", false, Set.of(), null); + TestUtils.assertEquals(rawHashSetAttribute, TestUtils.filter(schemaAttributes, "rawHashSet").orElse(null)); - static class TestWithParameterized { - String[] values; - @PII - List piiDataList; - @Encrypted - Map map; - } + final var rawMapAttribute = new MapAttribute("rawMap", false, Set.of(), null, null); + TestUtils.assertEquals(rawMapAttribute, TestUtils.filter(schemaAttributes, "rawMap").orElse(null)); - private java.util.Optional filter(Set schemaAttributes, - String name) { - return schemaAttributes.stream() - .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) - .findFirst(); + final var rawSortedMapAttribute = new MapAttribute("rawSortedMap", false, Set.of(), null, null); + TestUtils.assertEquals(rawSortedMapAttribute, TestUtils.filter(schemaAttributes, "rawSortedMap").orElse(null)); } - private void assertEquals(SchemaAttribute expected, - SchemaAttribute original) { - if (Objects.isNull(expected) && Objects.isNull(original)) { - return; - } - Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch"); - Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch"); - Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); - - // Compare QualifierInfo - assertEquals(expected.getQualifiers(), original.getQualifiers()); - - // Accept the expected attribute type and perform specific validations - expected.accept(new SchemaAttributeAcceptor() { - @Override - public Void accept(BooleanAttribute attribute) { - Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute"); - return null; - } - - @Override - public Void accept(ByteAttribute attribute) { - Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute"); - return null; - } - - @Override - public Void accept(DoubleAttribute attribute) { - Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute"); - return null; - } - - @Override - public Void accept(EnumAttribute attribute) { - Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute"); - EnumAttribute originalEnum = (EnumAttribute) original; - Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch"); - return null; - } - - @Override - public Void accept(FloatAttribute attribute) { - Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute"); - return null; - } - - @Override - public Void accept(IntegerAttribute attribute) { - Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute"); - return null; - } - - @Override - public Void accept(LongAttribute attribute) { - Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute"); - return null; - } + @Test + void testSchemaAttributes_WithObjects() { + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestObjectStub.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(5, schemaAttributes.size()); - @Override - public Void accept(StringAttribute attribute) { - Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute"); - return null; - } + final var objectAttribute = new ObjectAttribute("object", false, Set.of(), null); + TestUtils.assertEquals(objectAttribute, TestUtils.filter(schemaAttributes, "object").orElse(null)); - @Override - public Void accept(ArrayAttribute attribute) { - Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); - ArrayAttribute originalArray = (ArrayAttribute) original; - SchemaAttributeUtilsTest.this.assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute - return null; - } + final var objectsAttribute = new ArrayAttribute("objects", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + TestUtils.assertEquals(objectsAttribute, TestUtils.filter(schemaAttributes, "objects").orElse(null)); - @Override - public Void accept(MapAttribute attribute) { - Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); - MapAttribute originalMap = (MapAttribute) original; - SchemaAttributeUtilsTest.this.assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key - SchemaAttributeUtilsTest.this.assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value - return null; - } + final var objectListAttribute = new ArrayAttribute("objectList", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + TestUtils.assertEquals(objectListAttribute, TestUtils.filter(schemaAttributes, "objectList").orElse(null)); - @Override - public Void accept(ObjectAttribute attribute) { - Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute"); - ObjectAttribute originalObject = (ObjectAttribute) original; + final var objectSetAttribute = new ArrayAttribute("objectSet", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + TestUtils.assertEquals(objectSetAttribute, TestUtils.filter(schemaAttributes, "objectSet").orElse(null)); - // Recursive comparison of nested attributes - Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(), - "Nested attributes size mismatch"); - Iterator expectedIterator = attribute.getNestedAttributes().iterator(); - Iterator originalIterator = originalObject.getNestedAttributes().iterator(); + final var objectMapAttribute = new MapAttribute("objectMap", false, Set.of(), + new StringAttribute("key", false, Set.of()), + new ObjectAttribute("value", false, Set.of(), null)); + TestUtils.assertEquals(objectMapAttribute, TestUtils.filter(schemaAttributes, "objectMap").orElse(null)); - while (expectedIterator.hasNext() && originalIterator.hasNext()) { - SchemaAttributeUtilsTest.this.assertEquals(expectedIterator.next(), originalIterator.next()); - } - return null; - } - }); } - private void assertEquals(Set expected, - Set original) { - Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); - Assertions.assertNotNull(original, "Actual qualifiers should not be null"); - Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); - - expected.forEach(expectedQualifier -> { - java.util.Optional matchingQualifier = QualifierUtils.filter(original, expectedQualifier.getType()); - - Assertions.assertTrue(matchingQualifier.isPresent(), - "Missing qualifier of type: " + expectedQualifier.getType()); - - if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) { - Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier"); - - ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier; - ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get(); - - Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(), - "Mismatch in TTL seconds for SHORT_LIVED qualifier"); - } - }); + static class PrimitiveTestClass { + @Optional + String name; + int id; } + } \ No newline at end of file diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java index 321d09e..a231c02 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java @@ -1,5 +1,9 @@ package com.grookage.leia.common.utils; +import com.grookage.leia.common.stubs.NestedStub; +import com.grookage.leia.common.stubs.TestObjectStub; +import com.grookage.leia.common.stubs.TestParameterizedStub; +import com.grookage.leia.common.stubs.TestRawCollectionStub; import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; @@ -9,12 +13,10 @@ import com.grookage.leia.models.attributes.SchemaAttribute; import com.grookage.leia.models.attributes.StringAttribute; import com.grookage.leia.models.schema.SchemaValidationType; -import lombok.Builder; -import lombok.Data; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Map; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -202,48 +204,43 @@ void testInvalidMapValueType() throws Exception { assertEquals(1, errors.size()); } + @SneakyThrows @Test void testValidateNested() { - final var testRecord = TestRecord.builder() - .id(100) - .name("name") - .nestedObjectsList(List.of(NestedObject.builder() - .key("key") - .version(5l) - .enumclass(Enumclass.ONE) - .build(), NestedObject.builder() - .key("key") - .version(6l) - .enumclass(Enumclass.TWO) - .build())) - .nestedObjectMap(Map.of(Enumclass.ONE, NestedObject.builder() - .key("key") - .version(7l) - .enumclass(Enumclass.ONE) - .build())) - .build(); - // TODO::Abhishek Finish this - final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(testRecord); + final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validNestedStub.json", + NestedStub.class)); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(NestedStub.class); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + assertTrue(errors.isEmpty()); } - static enum Enumclass { - ONE, - TWO + @SneakyThrows + @Test + void testValidateParameterizedStub() { + final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validParameterizedStub.json", + TestParameterizedStub.class)); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestParameterizedStub.class); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + assertTrue(errors.isEmpty()); } - @Data - @Builder - static class NestedObject{ - String key; - long version; - Enumclass enumclass; + + @SneakyThrows + @Test + void testObjectValidation() { + final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validObjectStub.json", + TestObjectStub.class)); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestObjectStub.class); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + Assertions.assertTrue(errors.isEmpty()); } - @Data - @Builder - static class TestRecord { - String name; - int id; - List nestedObjectsList; - Map nestedObjectMap; + @SneakyThrows + @Test + void testRawCollectionSchemaValidation() { + final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validRawCollectionStub.json", + TestRawCollectionStub.class)); + final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRawCollectionStub.class); + final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + Assertions.assertTrue(errors.isEmpty()); } } \ No newline at end of file diff --git a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java index 8784eb8..0dda2d7 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java @@ -5,7 +5,6 @@ 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.DataType; import com.grookage.leia.models.attributes.DoubleAttribute; import com.grookage.leia.models.attributes.EnumAttribute; import com.grookage.leia.models.attributes.FloatAttribute; @@ -17,7 +16,6 @@ import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaValidationType; import lombok.SneakyThrows; -import org.apache.commons.lang3.ClassUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -80,6 +78,9 @@ void testAllFields() { final var mapAttribute = new MapAttribute("testAttribute", true, null, stringAttribute, stringAttribute); Assertions.assertTrue(SchemaValidationUtils.valid(Map.class, mapAttribute)); + final var plainObjectAttribute = new ObjectAttribute("testAttribute", true, null, null); + Assertions.assertTrue(SchemaValidationUtils.valid(Object.class, plainObjectAttribute)); + final var objectAttribute = new ObjectAttribute("testAttribute", true, null, Set.of(stringAttribute)); Assertions.assertTrue(SchemaValidationUtils.valid(SchemaDetails.class, objectAttribute)); @@ -223,12 +224,4 @@ static class GenericArrayTestClass { List[] arrayAttribute; } - @Test - void testIsAssignableFrom() { - Class primitive = long.class; - final var dataType = DataType.LONG; - Assertions.assertTrue(primitive.isPrimitive()); - Assertions.assertTrue(ClassUtils.isAssignable(primitive, dataType.getAssignableClass())); - } - } \ No newline at end of file From 24444eb77a108c67e971f25f76f30572f8e27992 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Sun, 1 Dec 2024 14:06:07 +0530 Subject: [PATCH 07/12] adds support for plain objects, bug fixes and tests --- .../com/grookage/leia/common/TestUtils.java | 164 ++++++++++++++++++ .../leia/common/stubs/NestedStub.java | 23 +++ .../grookage/leia/common/stubs/PIIData.java | 17 ++ .../leia/common/stubs/RecordStub.java | 12 ++ .../grookage/leia/common/stubs/TestEnum.java | 6 + .../leia/common/stubs/TestObjectStub.java | 20 +++ .../common/stubs/TestParameterizedStub.java | 21 +++ .../common/stubs/TestRawCollectionStub.java | 24 +++ .../test/resources/stubs/validNestedStub.json | 14 ++ .../test/resources/stubs/validObjectStub.json | 26 +++ .../stubs/validParameterizedStub.json | 21 +++ .../stubs/validRawCollectionStub.json | 38 ++++ 12 files changed, 386 insertions(+) create mode 100644 leia-common/src/test/java/com/grookage/leia/common/TestUtils.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/TestParameterizedStub.java create mode 100644 leia-common/src/test/java/com/grookage/leia/common/stubs/TestRawCollectionStub.java create mode 100644 leia-common/src/test/resources/stubs/validNestedStub.json create mode 100644 leia-common/src/test/resources/stubs/validObjectStub.json create mode 100644 leia-common/src/test/resources/stubs/validParameterizedStub.json create mode 100644 leia-common/src/test/resources/stubs/validRawCollectionStub.json diff --git a/leia-common/src/test/java/com/grookage/leia/common/TestUtils.java b/leia-common/src/test/java/com/grookage/leia/common/TestUtils.java new file mode 100644 index 0000000..70b7a02 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/TestUtils.java @@ -0,0 +1,164 @@ +package com.grookage.leia.common; + +import com.grookage.leia.common.utils.QualifierUtils; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.SchemaAttributeAcceptor; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.QualifierInfo; +import com.grookage.leia.models.qualifiers.QualifierType; +import com.grookage.leia.models.qualifiers.ShortLivedQualifier; +import lombok.experimental.UtilityClass; +import org.junit.jupiter.api.Assertions; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +@UtilityClass +public class TestUtils { + public Optional filter(Set schemaAttributes, + String name) { + return schemaAttributes.stream() + .filter(schemaAttribute -> schemaAttribute.getName().equals(name)) + .findFirst(); + } + + public void assertEquals(SchemaAttribute expected, + SchemaAttribute original) { + if (Objects.isNull(expected) && Objects.isNull(original)) { + return; + } + Assertions.assertEquals(expected.getType(), original.getType(), "Type mismatch"); + Assertions.assertEquals(expected.getName(), original.getName(), "Name mismatch"); + Assertions.assertEquals(expected.isOptional(), original.isOptional(), "Optionality mismatch"); + + // Compare QualifierInfo + assertEquals(expected.getQualifiers(), original.getQualifiers()); + + // Accept the expected attribute type and perform specific validations + expected.accept(new SchemaAttributeAcceptor() { + @Override + public Void accept(BooleanAttribute attribute) { + Assertions.assertInstanceOf(BooleanAttribute.class, original, "Original is not BooleanAttribute"); + return null; + } + + @Override + public Void accept(ByteAttribute attribute) { + Assertions.assertInstanceOf(ByteAttribute.class, original, "Original is not ByteAttribute"); + return null; + } + + @Override + public Void accept(DoubleAttribute attribute) { + Assertions.assertInstanceOf(DoubleAttribute.class, original, "Original is not DoubleAttribute"); + return null; + } + + @Override + public Void accept(EnumAttribute attribute) { + Assertions.assertInstanceOf(EnumAttribute.class, original, "Original is not EnumAttribute"); + EnumAttribute originalEnum = (EnumAttribute) original; + Assertions.assertEquals(attribute.getValues(), originalEnum.getValues(), "Enum values mismatch"); + return null; + } + + @Override + public Void accept(FloatAttribute attribute) { + Assertions.assertInstanceOf(FloatAttribute.class, original, "Original is not FloatAttribute"); + return null; + } + + @Override + public Void accept(IntegerAttribute attribute) { + Assertions.assertInstanceOf(IntegerAttribute.class, original, "Original is not IntegerAttribute"); + return null; + } + + @Override + public Void accept(LongAttribute attribute) { + Assertions.assertInstanceOf(LongAttribute.class, original, "Original is not LongAttribute"); + return null; + } + + @Override + public Void accept(StringAttribute attribute) { + Assertions.assertInstanceOf(StringAttribute.class, original, "Original is not StringAttribute"); + return null; + } + + @Override + public Void accept(ArrayAttribute attribute) { + Assertions.assertInstanceOf(ArrayAttribute.class, original, "Original is not ArrayAttribute"); + ArrayAttribute originalArray = (ArrayAttribute) original; + assertEquals(attribute.getElementAttribute(), originalArray.getElementAttribute()); // Recursive comparison for elementAttribute + return null; + } + + @Override + public Void accept(MapAttribute attribute) { + Assertions.assertInstanceOf(MapAttribute.class, original, "Original is not MapAttribute"); + MapAttribute originalMap = (MapAttribute) original; + assertEquals(attribute.getKeyAttribute(), originalMap.getKeyAttribute()); // Recursive comparison for key + assertEquals(attribute.getValueAttribute(), originalMap.getValueAttribute()); // Recursive comparison for value + return null; + } + + @Override + public Void accept(ObjectAttribute attribute) { + Assertions.assertInstanceOf(ObjectAttribute.class, original, "Original is not ObjectAttribute"); + ObjectAttribute originalObject = (ObjectAttribute) original; + + if (Objects.isNull(attribute.getNestedAttributes()) && Objects.isNull(originalObject.getNestedAttributes())) { + return null; + } + + // Recursive comparison of nested attributes + Assertions.assertEquals(attribute.getNestedAttributes().size(), originalObject.getNestedAttributes().size(), + "Nested attributes size mismatch"); + Iterator expectedIterator = attribute.getNestedAttributes().iterator(); + Iterator originalIterator = originalObject.getNestedAttributes().iterator(); + + while (expectedIterator.hasNext() && originalIterator.hasNext()) { + assertEquals(expectedIterator.next(), originalIterator.next()); + } + return null; + } + }); + } + + public void assertEquals(Set expected, + Set original) { + Assertions.assertNotNull(expected, "Expected qualifiers should not be null"); + Assertions.assertNotNull(original, "Actual qualifiers should not be null"); + Assertions.assertEquals(expected.size(), original.size(), "Qualifier sets size mismatch"); + + expected.forEach(expectedQualifier -> { + Optional matchingQualifier = QualifierUtils.filter(original, expectedQualifier.getType()); + + Assertions.assertTrue(matchingQualifier.isPresent(), + "Missing qualifier of type: " + expectedQualifier.getType()); + + if (expectedQualifier.getType() == QualifierType.SHORT_LIVED) { + Assertions.assertInstanceOf(ShortLivedQualifier.class, matchingQualifier.get(), "Actual SHORT_LIVED qualifier must be of type ShortLivedQualifier"); + + ShortLivedQualifier expectedShortLived = (ShortLivedQualifier) expectedQualifier; + ShortLivedQualifier actualShortLived = (ShortLivedQualifier) matchingQualifier.get(); + + Assertions.assertEquals(expectedShortLived.getTtlSeconds(), actualShortLived.getTtlSeconds(), + "Mismatch in TTL seconds for SHORT_LIVED qualifier"); + } + }); + } +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java new file mode 100644 index 0000000..d30b784 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/NestedStub.java @@ -0,0 +1,23 @@ +package com.grookage.leia.common.stubs; + +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class NestedStub { + String name; + int id; + @PII + @Encrypted + PIIData piiData; + @Encrypted + RecordStub recordStub; + TestEnum enumClass; + @PII + String phoneNumber; +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java new file mode 100644 index 0000000..351551d --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/PIIData.java @@ -0,0 +1,17 @@ +package com.grookage.leia.common.stubs; + +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@PII +@Data +@Builder +@Jacksonized +public class PIIData { + String name; + @Encrypted + String accountNumber; +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java new file mode 100644 index 0000000..cdd8316 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/RecordStub.java @@ -0,0 +1,12 @@ +package com.grookage.leia.common.stubs; + +import com.grookage.leia.models.annotations.attribute.Optional; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + +@Builder +@Jacksonized +public record RecordStub(@PII String name, + @Optional int id) { +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java new file mode 100644 index 0000000..c655da6 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestEnum.java @@ -0,0 +1,6 @@ +package com.grookage.leia.common.stubs; + +public enum TestEnum { + ONE, + TWO +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java new file mode 100644 index 0000000..d1f6c19 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestObjectStub.java @@ -0,0 +1,20 @@ +package com.grookage.leia.common.stubs; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Data +@Builder +@Jacksonized +public class TestObjectStub { + Object object; + Object[] objects; + List objectList; + Set objectSet; + Map objectMap; +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestParameterizedStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestParameterizedStub.java new file mode 100644 index 0000000..8093af3 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestParameterizedStub.java @@ -0,0 +1,21 @@ +package com.grookage.leia.common.stubs; + +import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; +import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +import java.util.List; +import java.util.Map; + +@Data +@Builder +@Jacksonized +public class TestParameterizedStub { + String[] values; + @PII + List piiDataList; + @Encrypted + Map map; +} diff --git a/leia-common/src/test/java/com/grookage/leia/common/stubs/TestRawCollectionStub.java b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestRawCollectionStub.java new file mode 100644 index 0000000..b5c5123 --- /dev/null +++ b/leia-common/src/test/java/com/grookage/leia/common/stubs/TestRawCollectionStub.java @@ -0,0 +1,24 @@ +package com.grookage.leia.common.stubs; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; + +@Data +@Builder +@Jacksonized +public class TestRawCollectionStub { + List rawList; + LinkedList rawLinkedList; + Set rawSet; + HashSet rawHashSet; + Map rawMap; + SortedMap rawSortedMap; +} diff --git a/leia-common/src/test/resources/stubs/validNestedStub.json b/leia-common/src/test/resources/stubs/validNestedStub.json new file mode 100644 index 0000000..12e67ed --- /dev/null +++ b/leia-common/src/test/resources/stubs/validNestedStub.json @@ -0,0 +1,14 @@ +{ + "name": "John Doe", + "id": 123, + "piiData": { + "name": "John Doe", + "accountNumber": "1234-5678-9012" + }, + "recordStub": { + "name": "Record Name", + "id": 456 + }, + "enumClass": "ONE", + "phoneNumber": "555-1234" +} \ No newline at end of file diff --git a/leia-common/src/test/resources/stubs/validObjectStub.json b/leia-common/src/test/resources/stubs/validObjectStub.json new file mode 100644 index 0000000..627cf8e --- /dev/null +++ b/leia-common/src/test/resources/stubs/validObjectStub.json @@ -0,0 +1,26 @@ +{ + "object": "Sample String", + "objects": [ + true, + false, + true + ], + "objectList": [ + "String 1", + "String 2", + "String 3" + ], + "objectSet": [ + 1, + 2, + 3 + ], + "objectMap": { + "ONE": { + "key": "value 1" + }, + "TWO": { + "key": "value 2" + } + } +} \ No newline at end of file diff --git a/leia-common/src/test/resources/stubs/validParameterizedStub.json b/leia-common/src/test/resources/stubs/validParameterizedStub.json new file mode 100644 index 0000000..02343f7 --- /dev/null +++ b/leia-common/src/test/resources/stubs/validParameterizedStub.json @@ -0,0 +1,21 @@ +{ + "values": [ + "value1", + "value2", + "value3" + ], + "piiDataList": [ + { + "name": "John Doe", + "accountNumber": "1234-5678-9012" + }, + { + "name": "Jane Smith", + "accountNumber": "9876-5432-1098" + } + ], + "map": { + "ONE": "Value 1", + "TWO": "Value 2" + } +} \ No newline at end of file diff --git a/leia-common/src/test/resources/stubs/validRawCollectionStub.json b/leia-common/src/test/resources/stubs/validRawCollectionStub.json new file mode 100644 index 0000000..afe354b --- /dev/null +++ b/leia-common/src/test/resources/stubs/validRawCollectionStub.json @@ -0,0 +1,38 @@ +{ + "rawList": [ + 42, + 87, + 19 + ], + "rawLinkedList": [ + 10000000000, + 20000000000, + 30000000000 + ], + "rawSet": [ + true, + false, + true + ], + "rawHashSet": [ + 3.14, + 2.71, + 1.41 + ], + "rawMap": { + "1": 123, + "2": 456, + "3": 789 + }, + "rawSortedMap": { + "1": { + "key": "Nested Value 1" + }, + "2": { + "key": "Nested Value 2" + }, + "3": { + "key": "Nested Value 3" + } + } +} \ No newline at end of file From c989e4205a279262120ba17249bc03bea630f8d9 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Sun, 1 Dec 2024 15:32:26 +0530 Subject: [PATCH 08/12] pom changes and change log addition --- CHANGELOG.md | 7 +++++-- leia-schema-validator/pom.xml | 11 ----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75786f4..59b15df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,11 @@ All notable changes to this project will be documented in this file. ## [0.0.1-RC7] -- LeiaClient - SchemaUtil to add support for building SchemaAttributes from the Schema class -- LeiaCore - Added API support to validate schema payload against the SchemaDetails of a specified key +- Introduced utils classes for building SchemaAttributes from Class and validating a json payload + against the SchemaDetails of a specified schema key +- Introduced a leia-common to host all the common utils classes +- Removed the SchemaValidatable annotation and moved it to a generic SchemaDefinition +- Few Bug fixes in SchemaValidationUtils ## [0.0.1-RC6] diff --git a/leia-schema-validator/pom.xml b/leia-schema-validator/pom.xml index 5ce41cd..c6e3ed2 100644 --- a/leia-schema-validator/pom.xml +++ b/leia-schema-validator/pom.xml @@ -57,17 +57,6 @@ ${lang3.version} - - leia-models - test-jar - - - * - * - - - com.grookage.leia - \ No newline at end of file From 89612928a2ac7576b6a7d30b99bdc38520a113c5 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Sun, 1 Dec 2024 15:52:38 +0530 Subject: [PATCH 09/12] review changes --- CHANGELOG.md | 4 +-- .../leia/common/utils/QualifierUtils.java | 26 +++++++-------- .../common/utils/SchemaAttributeUtils.java | 22 ++++++------- .../com/grookage/leia/common/utils/Utils.java | 6 ++-- .../leia/common/utils/ValidationUtils.java | 33 ++++++++++--------- 5 files changed, 46 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b15df..3c5705a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ All notable changes to this project will be documented in this file. - Introduced utils classes for building SchemaAttributes from Class and validating a json payload against the SchemaDetails of a specified schema key -- Introduced a leia-common to host all the common utils classes -- Removed the SchemaValidatable annotation and moved it to a generic SchemaDefinition +- Introduced a `leia-common` module to host all the common utils classes +- Removed the `SchemaValidatable` annotation and moved it to a generic `SchemaDefinition` - Few Bug fixes in SchemaValidationUtils ## [0.0.1-RC6] diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java index 4e8ad9f..241b6fa 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java @@ -45,40 +45,40 @@ public Optional filter(final Set qualifiers, .findFirst(); } - public Set getQualifierInfo(Type type) { + public Set getQualifierInfo(final Type type) { if (type instanceof Class klass) { return getQualifierInfo(klass); } return new HashSet<>(); } - public Set getQualifierInfo(Field field) { - Set qualifierInfos = new HashSet<>(); + public Set getQualifierInfo(final Field field) { + Set qualifiers = new HashSet<>(); if (field.isAnnotationPresent(Encrypted.class)) { - qualifierInfos.add(new EncryptedQualifier()); + qualifiers.add(new EncryptedQualifier()); } if (field.isAnnotationPresent(PII.class)) { - qualifierInfos.add(new PIIQualifier()); + qualifiers.add(new PIIQualifier()); } if (field.isAnnotationPresent(ShortLived.class)) { final var shortLived = field.getAnnotation(ShortLived.class); - qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + qualifiers.add(new ShortLivedQualifier(shortLived.ttlSeconds())); } - return qualifierInfos; + return qualifiers; } - public Set getQualifierInfo(Class klass) { - Set qualifierInfos = new HashSet<>(); + public Set getQualifierInfo(final Class klass) { + Set qualifiers = new HashSet<>(); if (klass.isAnnotationPresent(Encrypted.class)) { - qualifierInfos.add(new EncryptedQualifier()); + qualifiers.add(new EncryptedQualifier()); } if (klass.isAnnotationPresent(PII.class)) { - qualifierInfos.add(new PIIQualifier()); + qualifiers.add(new PIIQualifier()); } if (klass.isAnnotationPresent(ShortLived.class)) { final var shortLived = klass.getAnnotation(ShortLived.class); - qualifierInfos.add(new ShortLivedQualifier(shortLived.ttlSeconds())); + qualifiers.add(new ShortLivedQualifier(shortLived.ttlSeconds())); } - return qualifierInfos; + return qualifiers; } } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java index 58e4110..d9a78d7 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java @@ -98,10 +98,10 @@ private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedTyp throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); } - private SchemaAttribute handleMap(ParameterizedType parameterizedType, - String name, - Set qualifiers, - boolean optional) { + private SchemaAttribute handleMap(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, + final boolean optional) { final var keyType = parameterizedType.getActualTypeArguments()[0]; final var valueType = parameterizedType.getActualTypeArguments()[1]; return new MapAttribute( @@ -113,9 +113,9 @@ private SchemaAttribute handleMap(ParameterizedType parameterizedType, ); } - private SchemaAttribute handleCollection(ParameterizedType parameterizedType, - String name, - Set qualifiers, boolean optional) { + private SchemaAttribute handleCollection(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, boolean optional) { final var elementType = parameterizedType.getActualTypeArguments()[0]; return new ArrayAttribute( name, @@ -143,7 +143,7 @@ private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, private SchemaAttribute schemaAttribute(final Class klass, final String name, - Set qualifiers, + final Set qualifiers, final boolean optional) { if (klass == String.class) { return new StringAttribute(name, optional, qualifiers); @@ -213,18 +213,18 @@ private SchemaAttribute handlePrimitive(final Class klass, } - private boolean isOptional(Type type) { + private boolean isOptional(final Type type) { if (type instanceof Class klass) { return isOptional(klass); } return false; } - private boolean isOptional(Class klass) { + private boolean isOptional(final Class klass) { return klass.isAnnotationPresent(Optional.class); } - private boolean isOptional(Field field) { + private boolean isOptional(final Field field) { return field.isAnnotationPresent(Optional.class); } } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java index 11c128e..4b37ecf 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java @@ -31,7 +31,7 @@ @UtilityClass public class Utils { - public List getAllFields(Class type) { + public List getAllFields(final Class type) { List fields = new ArrayList<>(); for (Class c = type; c != null; c = c.getSuperclass()) { fields.addAll(Arrays.asList(c.getDeclaredFields())); @@ -39,7 +39,7 @@ public List getAllFields(Class type) { return fields; } - public Type[] getTypeArguments(ParameterizedType parameterizedType) { + public Type[] getTypeArguments(final ParameterizedType parameterizedType) { final var typeArguments = parameterizedType.getActualTypeArguments(); if (typeArguments.length == 0) { throw SchemaValidationException.error(ValidationErrorCode.INVALID_SCHEMAS, @@ -48,7 +48,7 @@ public Type[] getTypeArguments(ParameterizedType parameterizedType) { return typeArguments; } - public Set getEnumValues(Class klass) { + public Set getEnumValues(final Class klass) { return Arrays.stream(klass.getEnumConstants()) .map(enumConstant -> ((Enum) enumConstant).name()) .collect(Collectors.toSet()); diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java index 6006e31..8b695ba 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java @@ -43,9 +43,9 @@ @UtilityClass public class ValidationUtils { - public static List validate(JsonNode jsonNode, - SchemaValidationType validationType, - Set schemaAttributes) { + public static List validate(final JsonNode jsonNode, + final SchemaValidationType validationType, + final Set schemaAttributes) { List validationErrors = new ArrayList<>(); Map schemaMap = new HashMap<>(); @@ -84,10 +84,10 @@ public static List validate(JsonNode jsonNode, return validationErrors; } - private void validateField(JsonNode fieldNode, - SchemaAttribute attribute, - SchemaValidationType validationType, - List validationErrors) { + private void validateField(final JsonNode fieldNode, + final SchemaAttribute attribute, + final SchemaValidationType validationType, + final List validationErrors) { final var fieldName = attribute.getName(); if (!isMatchingType(fieldNode, attribute)) { validationErrors.add("Type mismatch for field: " + fieldName + @@ -111,10 +111,10 @@ private void validateField(JsonNode fieldNode, } } - private void validateCollectionAttribute(JsonNode fieldNode, - ArrayAttribute arrayAttribute, - SchemaValidationType schemaValidationType, - List validationErrors) { + private void validateCollectionAttribute(final JsonNode fieldNode, + final ArrayAttribute arrayAttribute, + final SchemaValidationType schemaValidationType, + final List validationErrors) { // Handling Non-Parameterized Collections (eg: List.class, Set.class, Map.class etc.) if (arrayAttribute.getElementAttribute() == null) { return; @@ -125,10 +125,10 @@ private void validateCollectionAttribute(JsonNode fieldNode, } } - private void validateMapAttribute(JsonNode fieldNode, - MapAttribute mapAttribute, - SchemaValidationType schemaValidationType, - List validationErrors) { + private void validateMapAttribute(final JsonNode fieldNode, + final MapAttribute mapAttribute, + final SchemaValidationType schemaValidationType, + final List validationErrors) { // Handling Raw Map.class if (Objects.isNull(mapAttribute.getKeyAttribute()) && Objects.isNull(mapAttribute.getValueAttribute())) { return; @@ -150,7 +150,8 @@ private void validateMapAttribute(JsonNode fieldNode, } - private boolean isMatchingType(JsonNode fieldNode, SchemaAttribute attribute) { + private boolean isMatchingType(final JsonNode fieldNode, + final SchemaAttribute attribute) { return attribute.accept(new SchemaAttributeAcceptor<>() { @Override public Boolean accept(BooleanAttribute attribute) { From 0a5fd98254f407578f546a6de0f5d42fd2ce8597 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Sun, 1 Dec 2024 19:34:49 +0530 Subject: [PATCH 10/12] adding leia-common dependency to client --- leia-client/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/leia-client/pom.xml b/leia-client/pom.xml index cfbb232..d1f72eb 100644 --- a/leia-client/pom.xml +++ b/leia-client/pom.xml @@ -74,6 +74,11 @@ leia-models + + com.grookage.leia + leia-common + + com.grookage.leia leia-schema-validator From 5ba56cbabf5889477273620160137c0f5ccfae42 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Mon, 2 Dec 2024 09:26:51 +0530 Subject: [PATCH 11/12] code refactoring --- README.md | 2 ++ .../SchemaValidationUtils.java | 3 +- .../SchemaPayloadValidator.java} | 4 +-- .../SchemaValidationUtilsTest.java | 2 +- .../SchemaPayloadValidatorTest.java} | 29 ++++++++++--------- .../bundle/resources/SchemaResource.java | 5 ++-- .../leia/validator/StaticSchemaValidator.java | 2 +- 7 files changed, 25 insertions(+), 22 deletions(-) rename leia-common/src/main/java/com/grookage/leia/common/{validation => utils}/SchemaValidationUtils.java (99%) rename leia-common/src/main/java/com/grookage/leia/common/{utils/ValidationUtils.java => validation/SchemaPayloadValidator.java} (99%) rename leia-common/src/test/java/com/grookage/leia/common/{validation => utils}/SchemaValidationUtilsTest.java (99%) rename leia-common/src/test/java/com/grookage/leia/common/{utils/ValidationUtilsTest.java => validation/SchemaPayloadValidatorTest.java} (83%) diff --git a/README.md b/README.md index 0975d14..661a4d8 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ A sample schema looks like the following the `LeiaMessageProduceClient`, will multiplex the testSchema to both the versions, the transformationTargets ought to be jsonPathRules. +Please refer to the `SchemaBuilder` class which can be used to generate schema against a class. + #### Using the LeiaClientBundle On the client side, please use the `LeiaClientBundle` diff --git a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java similarity index 99% rename from leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java rename to leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java index dd19b9b..bccb142 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaValidationUtils.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package com.grookage.leia.common.validation; +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.common.utils.Utils; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.MapAttribute; import com.grookage.leia.models.attributes.ObjectAttribute; diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java similarity index 99% rename from leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java rename to leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java index 8b695ba..f12cf98 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/ValidationUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/validation/SchemaPayloadValidator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.grookage.leia.common.utils; +package com.grookage.leia.common.validation; import com.fasterxml.jackson.databind.JsonNode; import com.grookage.leia.models.attributes.ArrayAttribute; @@ -42,7 +42,7 @@ import java.util.Set; @UtilityClass -public class ValidationUtils { +public class SchemaPayloadValidator { public static List validate(final JsonNode jsonNode, final SchemaValidationType validationType, final Set schemaAttributes) { diff --git a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java similarity index 99% rename from leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java rename to leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java index 0dda2d7..1944db9 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaValidationUtilsTest.java @@ -1,4 +1,4 @@ -package com.grookage.leia.common.validation; +package com.grookage.leia.common.utils; import com.grookage.leia.common.exception.ValidationErrorCode; import com.grookage.leia.models.ResourceHelper; diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java similarity index 83% rename from leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java rename to leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java index a231c02..ccd57db 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/utils/ValidationUtilsTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java @@ -1,9 +1,10 @@ -package com.grookage.leia.common.utils; +package com.grookage.leia.common.validation; import com.grookage.leia.common.stubs.NestedStub; import com.grookage.leia.common.stubs.TestObjectStub; import com.grookage.leia.common.stubs.TestParameterizedStub; import com.grookage.leia.common.stubs.TestRawCollectionStub; +import com.grookage.leia.common.utils.SchemaAttributeUtils; import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; @@ -23,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -class ValidationUtilsTest { +class SchemaPayloadValidatorTest { @Test void testValidJsonAgainstSchema() throws Exception { final var jsonNode = ResourceHelper.getObjectMapper().readTree(""" @@ -40,7 +41,7 @@ void testValidJsonAgainstSchema() throws Exception { new BooleanAttribute("isActive", false, null) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -62,7 +63,7 @@ void testUnexpectedFieldInJson() throws Exception { new BooleanAttribute("isActive", false, null) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -82,7 +83,7 @@ void testMissingRequiredField() throws Exception { new IntegerAttribute("age", false, null) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -103,7 +104,7 @@ void testTypeMismatch() throws Exception { new IntegerAttribute("age", false, null) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -130,7 +131,7 @@ void testNestedObjectValidation() throws Exception { new ObjectAttribute("user", false, null, nestedAttributes) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -147,7 +148,7 @@ void testArrayValidation() throws Exception { new ArrayAttribute("numbers", false, null, new IntegerAttribute("element", false, null)) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -173,7 +174,7 @@ void testMapValidation() throws Exception { ) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -198,7 +199,7 @@ void testInvalidMapValueType() throws Exception { ) ); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertFalse(errors.isEmpty()); assertEquals(1, errors.size()); @@ -210,7 +211,7 @@ void testValidateNested() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validNestedStub.json", NestedStub.class)); final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(NestedStub.class); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -220,7 +221,7 @@ void testValidateParameterizedStub() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validParameterizedStub.json", TestParameterizedStub.class)); final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestParameterizedStub.class); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -230,7 +231,7 @@ void testObjectValidation() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validObjectStub.json", TestObjectStub.class)); final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestObjectStub.class); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); Assertions.assertTrue(errors.isEmpty()); } @@ -240,7 +241,7 @@ void testRawCollectionSchemaValidation() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validRawCollectionStub.json", TestRawCollectionStub.class)); final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRawCollectionStub.class); - final var errors = ValidationUtils.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); + final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); Assertions.assertTrue(errors.isEmpty()); } } \ No newline at end of file diff --git a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java index 23569c4..2d1f56e 100644 --- a/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java +++ b/leia-dropwizard/src/main/java/com/grookage/leia/dropwizard/bundle/resources/SchemaResource.java @@ -19,7 +19,7 @@ import com.codahale.metrics.annotation.ExceptionMetered; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.databind.JsonNode; -import com.grookage.leia.common.utils.ValidationUtils; +import com.grookage.leia.common.validation.SchemaPayloadValidator; import com.grookage.leia.core.exception.LeiaErrorCode; import com.grookage.leia.core.exception.LeiaException; import com.grookage.leia.core.retrieval.SchemaRetriever; @@ -90,7 +90,8 @@ public GenericResponse> validateSchema(@Valid SchemaKey schemaKey, @Valid JsonNode jsonNode) { final var schemaDetails = schemaRetriever.getSchemaDetails(schemaKey) .orElseThrow(() -> LeiaException.error(LeiaErrorCode.NO_SCHEMA_FOUND)); - final var validationErrors = ValidationUtils.validate(jsonNode, schemaDetails.getValidationType(), schemaDetails.getAttributes()); + final var validationErrors = SchemaPayloadValidator.validate(jsonNode, schemaDetails.getValidationType(), + schemaDetails.getAttributes()); if (validationErrors.isEmpty()) { return GenericResponse.>builder() .success(true) 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 7f1df4d..5d599b8 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 @@ -18,7 +18,7 @@ import com.grookage.leia.common.exception.SchemaValidationException; import com.grookage.leia.common.exception.ValidationErrorCode; -import com.grookage.leia.common.validation.SchemaValidationUtils; +import com.grookage.leia.common.utils.SchemaValidationUtils; import com.grookage.leia.models.annotations.SchemaDefinition; import com.grookage.leia.models.schema.SchemaDetails; import com.grookage.leia.models.schema.SchemaKey; From 279ad6c8dc57271651eb9e5180244ce0688d5432 Mon Sep 17 00:00:00 2001 From: Gunda Abhishek Date: Mon, 2 Dec 2024 13:08:11 +0530 Subject: [PATCH 12/12] review changes and changelog additions --- CHANGELOG.md | 10 +- .../leia/common/builder/SchemaBuilder.java | 218 ++++++++++++++++- .../utils/{Utils.java => FieldUtils.java} | 23 +- .../leia/common/utils/QualifierUtils.java | 8 +- .../common/utils/SchemaAttributeUtils.java | 230 ------------------ .../common/utils/SchemaValidationUtils.java | 15 +- .../{TestUtils.java => LeiaTestUtils.java} | 2 +- .../common/builder/SchemaBuilderTest.java | 164 ++++++++++++- .../utils/SchemaAttributeUtilsTest.java | 170 ------------- .../SchemaPayloadValidatorTest.java | 10 +- 10 files changed, 407 insertions(+), 443 deletions(-) rename leia-common/src/main/java/com/grookage/leia/common/utils/{Utils.java => FieldUtils.java} (53%) delete mode 100644 leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java rename leia-common/src/test/java/com/grookage/leia/common/{TestUtils.java => LeiaTestUtils.java} (99%) delete mode 100644 leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c5705a..26d29ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,13 @@ All notable changes to this project will be documented in this file. ## [0.0.1-RC7] -- Introduced utils classes for building SchemaAttributes from Class and validating a json payload - against the SchemaDetails of a specified schema key - Introduced a `leia-common` module to host all the common utils classes -- Removed the `SchemaValidatable` annotation and moved it to a generic `SchemaDefinition` -- Few Bug fixes in SchemaValidationUtils +- Added annotation classes for the Qualifiers( PII, Encrypted, ShortLived) that can be added on the members of + the Schema class +- Replaced the `SchemaValidatable` annotation and moved it to a generic `SchemaDefinition` +- Introduced `SchemaBuilder`: For building the schema request against a class annotated with SchemaDefinition +- Introduced `SchemaPayloadValidator`: For validating a schema json payload against a specified SchemaKey +- Addressed issues in handling plain `Object` and Primitive Class types in `SchemaValidationUtils` ## [0.0.1-RC6] diff --git a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java index 1f8f76f..5a87de2 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java +++ b/leia-common/src/main/java/com/grookage/leia/common/builder/SchemaBuilder.java @@ -16,13 +16,36 @@ package com.grookage.leia.common.builder; -import com.grookage.leia.common.utils.SchemaAttributeUtils; +import com.grookage.leia.common.utils.FieldUtils; +import com.grookage.leia.common.utils.QualifierUtils; import com.grookage.leia.models.annotations.SchemaDefinition; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.BooleanAttribute; +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.SchemaAttribute; +import com.grookage.leia.models.attributes.StringAttribute; +import com.grookage.leia.models.qualifiers.QualifierInfo; import com.grookage.leia.models.schema.ingestion.CreateSchemaRequest; import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.ClassUtils; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; @UtilityClass public class SchemaBuilder { @@ -37,8 +60,199 @@ public Optional buildSchemaRequest(final Class klass) { .description(schemaDefinition.description()) .schemaType(schemaDefinition.type()) .validationType(schemaDefinition.validation()) - .attributes(SchemaAttributeUtils.getSchemaAttributes(klass)) + .attributes(getSchemaAttributes(klass)) .build() ); } + + public Set getSchemaAttributes(final Class klass) { + return FieldUtils.getAllFields(klass) + .stream() + .map(SchemaBuilder::schemaAttribute) + .collect(Collectors.toSet()); + } + + private SchemaAttribute schemaAttribute(final Field field) { + return schemaAttribute( + field.getGenericType(), + field.getName(), + QualifierUtils.getQualifiers(field), + isOptional(field) + ); + } + + private SchemaAttribute schemaAttribute(final Type type, + final String name, + final Set qualifiers, + final boolean optional) { + // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) + if (type instanceof Class klass) { + return schemaAttribute(klass, name, qualifiers, optional); + } + + // Handle ParameterizedType (e.g., List, Map) + if (type instanceof ParameterizedType parameterizedType) { + return schemaAttribute(parameterizedType, name, qualifiers, optional); + } + + // Handle GenericArrayType (e.g., T[], List) + if (type instanceof GenericArrayType genericArrayType) { + return schemaAttribute(genericArrayType, name, qualifiers, optional); + } + + throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); + } + + private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, + final boolean optional) { + final var rawType = (Class) parameterizedType.getRawType(); + // Handle List or Set + if (ClassUtils.isAssignable(rawType, Collection.class)) { + return handleCollection(parameterizedType, name, qualifiers, optional); + } + + // Handle Map + if (ClassUtils.isAssignable(rawType, Map.class)) { + return handleMap(parameterizedType, name, qualifiers, optional); + } + throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); + } + + private SchemaAttribute handleMap(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, + final boolean optional) { + final var keyType = parameterizedType.getActualTypeArguments()[0]; + final var valueType = parameterizedType.getActualTypeArguments()[1]; + return new MapAttribute( + name, + optional, + qualifiers, + schemaAttribute(keyType, "key", QualifierUtils.getQualifiers(keyType), isOptional(keyType)), + schemaAttribute(valueType, "value", QualifierUtils.getQualifiers(valueType), isOptional(valueType)) + ); + } + + private SchemaAttribute handleCollection(final ParameterizedType parameterizedType, + final String name, + final Set qualifiers, boolean optional) { + final var elementType = parameterizedType.getActualTypeArguments()[0]; + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(elementType, "element", QualifierUtils.getQualifiers(elementType), + isOptional(elementType)) + ); + } + + private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, + final String name, + final Set qualifiers, + final boolean optional) { + final var componentType = genericArrayType.getGenericComponentType(); + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(componentType, "element", QualifierUtils.getQualifiers(componentType), + isOptional(componentType)) + ); + } + + + private SchemaAttribute schemaAttribute(final Class klass, + final String name, + final Set qualifiers, + final boolean optional) { + if (klass == String.class) { + return new StringAttribute(name, optional, qualifiers); + } + + if (klass.isEnum()) { + return new EnumAttribute(name, optional, qualifiers, getEnumValues(klass)); + } + + // Handle int, long, boolean etc. + if (klass.isPrimitive()) { + return handlePrimitive(klass, name, qualifiers, optional); + } + + // Handle String[], Object[] etc. + if (klass.isArray()) { + final var componentType = klass.getComponentType(); + return new ArrayAttribute( + name, + optional, + qualifiers, + schemaAttribute(componentType, "element", QualifierUtils.getQualifiers(componentType), + isOptional(componentType)) + ); + } + + // Handle Raw List, Set + if (ClassUtils.isAssignable(klass, Collection.class)) { + return new ArrayAttribute(name, optional, qualifiers, null); + } + + // Handle Raw Map + if (ClassUtils.isAssignable(klass, Map.class)) { + return new MapAttribute(name, optional, qualifiers, null, null); + } + + if (klass.equals(Object.class)) { + return new ObjectAttribute(name, optional, qualifiers, null); + } + + // Handling custom defined POJO's + final var schemaAttributes = getSchemaAttributes(klass); + return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); + } + + private SchemaAttribute handlePrimitive(final Class klass, + final String name, + final Set qualifiers, + final boolean optional) { + if (klass == Integer.class || klass == int.class) { + return new IntegerAttribute(name, optional, qualifiers); + } + if (klass == Boolean.class || klass == boolean.class) { + return new BooleanAttribute(name, optional, qualifiers); + } + if (klass == Double.class || klass == double.class) { + return new DoubleAttribute(name, optional, qualifiers); + } + if (klass == Long.class || klass == long.class) { + return new LongAttribute(name, optional, qualifiers); + } + if (klass == Float.class || klass == float.class) { + return new FloatAttribute(name, optional, qualifiers); + } + + throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); + + } + + private Set getEnumValues(final Class klass) { + return Arrays.stream(klass.getEnumConstants()) + .map(enumConstant -> ((Enum) enumConstant).name()) + .collect(Collectors.toSet()); + } + + private boolean isOptional(final Type type) { + if (type instanceof Class klass) { + return isOptional(klass); + } + return false; + } + + private boolean isOptional(final Class klass) { + return klass.isAnnotationPresent(com.grookage.leia.models.annotations.attribute.Optional.class); + } + + private boolean isOptional(final Field field) { + return field.isAnnotationPresent(com.grookage.leia.models.annotations.attribute.Optional.class); + } } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java similarity index 53% rename from leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java rename to leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java index 4b37ecf..c60c815 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/Utils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/FieldUtils.java @@ -16,21 +16,15 @@ package com.grookage.leia.common.utils; -import com.grookage.leia.common.exception.SchemaValidationException; -import com.grookage.leia.common.exception.ValidationErrorCode; import lombok.experimental.UtilityClass; import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; @UtilityClass -public class Utils { +public class FieldUtils { public List getAllFields(final Class type) { List fields = new ArrayList<>(); for (Class c = type; c != null; c = c.getSuperclass()) { @@ -38,19 +32,4 @@ public List getAllFields(final Class type) { } return fields; } - - public 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)); - } - return typeArguments; - } - - public Set getEnumValues(final Class klass) { - return Arrays.stream(klass.getEnumConstants()) - .map(enumConstant -> ((Enum) enumConstant).name()) - .collect(Collectors.toSet()); - } } diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java index 241b6fa..6e8e3ee 100644 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java +++ b/leia-common/src/main/java/com/grookage/leia/common/utils/QualifierUtils.java @@ -45,14 +45,14 @@ public Optional filter(final Set qualifiers, .findFirst(); } - public Set getQualifierInfo(final Type type) { + public Set getQualifiers(final Type type) { if (type instanceof Class klass) { - return getQualifierInfo(klass); + return getQualifiers(klass); } return new HashSet<>(); } - public Set getQualifierInfo(final Field field) { + public Set getQualifiers(final Field field) { Set qualifiers = new HashSet<>(); if (field.isAnnotationPresent(Encrypted.class)) { qualifiers.add(new EncryptedQualifier()); @@ -67,7 +67,7 @@ public Set getQualifierInfo(final Field field) { return qualifiers; } - public Set getQualifierInfo(final Class klass) { + public Set getQualifiers(final Class klass) { Set qualifiers = new HashSet<>(); if (klass.isAnnotationPresent(Encrypted.class)) { qualifiers.add(new EncryptedQualifier()); diff --git a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java b/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java deleted file mode 100644 index d9a78d7..0000000 --- a/leia-common/src/main/java/com/grookage/leia/common/utils/SchemaAttributeUtils.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2024. Koushik R . - * - * Licensed 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 com.grookage.leia.common.utils; - -import com.grookage.leia.models.annotations.attribute.Optional; -import com.grookage.leia.models.attributes.ArrayAttribute; -import com.grookage.leia.models.attributes.BooleanAttribute; -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.SchemaAttribute; -import com.grookage.leia.models.attributes.StringAttribute; -import com.grookage.leia.models.qualifiers.QualifierInfo; -import lombok.experimental.UtilityClass; -import org.apache.commons.lang3.ClassUtils; - -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -@UtilityClass -public class SchemaAttributeUtils { - public Set getSchemaAttributes(final Class klass) { - return Utils.getAllFields(klass) - .stream() - .map(SchemaAttributeUtils::schemaAttribute) - .collect(Collectors.toSet()); - } - - private SchemaAttribute schemaAttribute(final Field field) { - return schemaAttribute( - field.getGenericType(), - field.getName(), - QualifierUtils.getQualifierInfo(field), - isOptional(field) - ); - } - - private SchemaAttribute schemaAttribute(final Type type, - final String name, - final Set qualifiers, - final boolean optional) { - // Handle Class instances (eg. String, Enum classes, Complex POJO Objects etc.) - if (type instanceof Class klass) { - return schemaAttribute(klass, name, qualifiers, optional); - } - - // Handle ParameterizedType (e.g., List, Map) - if (type instanceof ParameterizedType parameterizedType) { - return schemaAttribute(parameterizedType, name, qualifiers, optional); - } - - // Handle GenericArrayType (e.g., T[], List) - if (type instanceof GenericArrayType genericArrayType) { - return schemaAttribute(genericArrayType, name, qualifiers, optional); - } - - throw new UnsupportedOperationException("Unsupported field type: " + type.getTypeName()); - } - - private SchemaAttribute schemaAttribute(final ParameterizedType parameterizedType, - final String name, - final Set qualifiers, - final boolean optional) { - final var rawType = (Class) parameterizedType.getRawType(); - // Handle List or Set - if (ClassUtils.isAssignable(rawType, Collection.class)) { - return handleCollection(parameterizedType, name, qualifiers, optional); - } - - // Handle Map - if (ClassUtils.isAssignable(rawType, Map.class)) { - return handleMap(parameterizedType, name, qualifiers, optional); - } - throw new UnsupportedOperationException("Unsupported field type: " + parameterizedType.getTypeName()); - } - - private SchemaAttribute handleMap(final ParameterizedType parameterizedType, - final String name, - final Set qualifiers, - final boolean optional) { - final var keyType = parameterizedType.getActualTypeArguments()[0]; - final var valueType = parameterizedType.getActualTypeArguments()[1]; - return new MapAttribute( - name, - optional, - qualifiers, - schemaAttribute(keyType, "key", QualifierUtils.getQualifierInfo(keyType), isOptional(keyType)), - schemaAttribute(valueType, "value", QualifierUtils.getQualifierInfo(valueType), isOptional(valueType)) - ); - } - - private SchemaAttribute handleCollection(final ParameterizedType parameterizedType, - final String name, - final Set qualifiers, boolean optional) { - final var elementType = parameterizedType.getActualTypeArguments()[0]; - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(elementType, "element", QualifierUtils.getQualifierInfo(elementType), - isOptional(elementType)) - ); - } - - private SchemaAttribute schemaAttribute(final GenericArrayType genericArrayType, - final String name, - final Set qualifiers, - final boolean optional) { - final var componentType = genericArrayType.getGenericComponentType(); - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), - isOptional(componentType)) - ); - } - - - private SchemaAttribute schemaAttribute(final Class klass, - final String name, - final Set qualifiers, - final boolean optional) { - if (klass == String.class) { - return new StringAttribute(name, optional, qualifiers); - } - - if (klass.isEnum()) { - return new EnumAttribute(name, optional, qualifiers, Utils.getEnumValues(klass)); - } - - // Handle int, long, boolean etc. - if (klass.isPrimitive()) { - return handlePrimitive(klass, name, qualifiers, optional); - } - - // Handle String[], Object[] etc. - if (klass.isArray()) { - final var componentType = klass.getComponentType(); - return new ArrayAttribute( - name, - optional, - qualifiers, - schemaAttribute(componentType, "element", QualifierUtils.getQualifierInfo(componentType), - isOptional(componentType)) - ); - } - - // Handle Raw List, Set - if (ClassUtils.isAssignable(klass, Collection.class)) { - return new ArrayAttribute(name, optional, qualifiers, null); - } - - // Handle Raw Map - if (ClassUtils.isAssignable(klass, Map.class)) { - return new MapAttribute(name, optional, qualifiers, null, null); - } - - if (klass.equals(Object.class)) { - return new ObjectAttribute(name, optional, qualifiers, null); - } - - // Handling custom defined POJO's - final var schemaAttributes = getSchemaAttributes(klass); - return new ObjectAttribute(name, optional, qualifiers, schemaAttributes); - } - - private SchemaAttribute handlePrimitive(final Class klass, - final String name, - final Set qualifiers, - final boolean optional) { - if (klass == Integer.class || klass == int.class) { - return new IntegerAttribute(name, optional, qualifiers); - } - if (klass == Boolean.class || klass == boolean.class) { - return new BooleanAttribute(name, optional, qualifiers); - } - if (klass == Double.class || klass == double.class) { - return new DoubleAttribute(name, optional, qualifiers); - } - if (klass == Long.class || klass == long.class) { - return new LongAttribute(name, optional, qualifiers); - } - if (klass == Float.class || klass == float.class) { - return new FloatAttribute(name, optional, qualifiers); - } - - throw new UnsupportedOperationException("Unsupported primitive class type: " + klass.getName()); - - } - - private boolean isOptional(final Type type) { - if (type instanceof Class klass) { - return isOptional(klass); - } - return false; - } - - private boolean isOptional(final Class klass) { - return klass.isAnnotationPresent(Optional.class); - } - - private boolean isOptional(final Field field) { - return field.isAnnotationPresent(Optional.class); - } -} 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 bccb142..0afba08 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 @@ -63,7 +63,7 @@ public boolean valid(final SchemaValidationType validationType, final Set attributes, final Class klass) { - final var fields = Utils.getAllFields(klass); + final var fields = FieldUtils.getAllFields(klass); if (!validSchema(validationType, attributes, fields)) { return false; } @@ -176,7 +176,7 @@ public Boolean accept(ArrayAttribute attribute) { if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { return false; } - final var typeArguments = Utils.getTypeArguments(parameterizedType); + final var typeArguments = getTypeArguments(parameterizedType); return valid(validationType, attribute.getElementAttribute(), typeArguments[0]); } @@ -189,13 +189,22 @@ public Boolean accept(MapAttribute attribute) { if (!ClassUtils.isAssignable(rawType, attribute.getType().getAssignableClass())) { return false; } - final var typeArguments = Utils.getTypeArguments(parameterizedType); + final var typeArguments = getTypeArguments(parameterizedType); return valid(validationType, attribute.getKeyAttribute(), typeArguments[0]) && valid(validationType, attribute.getValueAttribute(), typeArguments[1]); } }); } + 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)); + } + return typeArguments; + } + private boolean valid(final SchemaValidationType validationType, final SchemaAttribute attribute, final GenericArrayType arrayType) { diff --git a/leia-common/src/test/java/com/grookage/leia/common/TestUtils.java b/leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java similarity index 99% rename from leia-common/src/test/java/com/grookage/leia/common/TestUtils.java rename to leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java index 70b7a02..331e35b 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/TestUtils.java +++ b/leia-common/src/test/java/com/grookage/leia/common/LeiaTestUtils.java @@ -26,7 +26,7 @@ import java.util.Set; @UtilityClass -public class TestUtils { +public class LeiaTestUtils { public Optional filter(Set schemaAttributes, String name) { return schemaAttributes.stream() diff --git a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java index 1314c47..3fb5d8f 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/builder/SchemaBuilderTest.java @@ -1,22 +1,40 @@ package com.grookage.leia.common.builder; -import com.grookage.leia.common.utils.SchemaAttributeUtils; +import com.grookage.leia.common.LeiaTestUtils; +import com.grookage.leia.common.stubs.NestedStub; +import com.grookage.leia.common.stubs.RecordStub; +import com.grookage.leia.common.stubs.TestEnum; +import com.grookage.leia.common.stubs.TestObjectStub; +import com.grookage.leia.common.stubs.TestParameterizedStub; +import com.grookage.leia.common.stubs.TestRawCollectionStub; import com.grookage.leia.models.annotations.SchemaDefinition; import com.grookage.leia.models.annotations.attribute.Optional; import com.grookage.leia.models.annotations.attribute.qualifiers.Encrypted; import com.grookage.leia.models.annotations.attribute.qualifiers.PII; +import com.grookage.leia.models.attributes.ArrayAttribute; +import com.grookage.leia.models.attributes.EnumAttribute; +import com.grookage.leia.models.attributes.IntegerAttribute; +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.StringAttribute; +import com.grookage.leia.models.qualifiers.EncryptedQualifier; +import com.grookage.leia.models.qualifiers.PIIQualifier; import com.grookage.leia.models.schema.SchemaType; import com.grookage.leia.models.schema.SchemaValidationType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.HashSet; +import java.util.Set; + class SchemaBuilderTest { @Test void testSchemaRequest() { final var schemaCreateRequest = SchemaBuilder.buildSchemaRequest(TestRecord.class) .orElse(null); Assertions.assertNotNull(schemaCreateRequest); - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRecord.class); + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestRecord.class); Assertions.assertEquals(TestRecord.NAME, schemaCreateRequest.getSchemaName()); Assertions.assertEquals(TestRecord.NAMESPACE, schemaCreateRequest.getNamespace()); Assertions.assertEquals(TestRecord.DESCRIPTION, schemaCreateRequest.getDescription()); @@ -31,6 +49,148 @@ void testSchemaRequest_WithInvalidClass() { Assertions.assertTrue(SchemaBuilder.buildSchemaRequest(TestObject.class).isEmpty()); } + @Test + void testSchemaAttributes_WithPrimitiveClass() { + final var schemaAttributeSet = SchemaBuilder.getSchemaAttributes(PrimitiveTestClass.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); + LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); + LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithRecordClass() { + final var schemaAttributeSet = SchemaBuilder.getSchemaAttributes(RecordStub.class); + Assertions.assertNotNull(schemaAttributeSet); + Assertions.assertEquals(2, schemaAttributeSet.size()); + final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); + LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributeSet, "name").orElse(null)); + final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); + LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributeSet, "id").orElse(null)); + } + + @Test + void testSchemaAttributes_WithNestedObject() { + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(NestedStub.class); + Assertions.assertFalse(schemaAttributes.isEmpty()); + Assertions.assertEquals(6, schemaAttributes.size()); + final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); + LeiaTestUtils.assertEquals(nameAttribute, LeiaTestUtils.filter(schemaAttributes, "name").orElse(null)); + + final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); + LeiaTestUtils.assertEquals(idAttribute, LeiaTestUtils.filter(schemaAttributes, "id").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); + LeiaTestUtils.assertEquals(piiDataAttribute, LeiaTestUtils.filter(schemaAttributes, "piiData").orElse(null)); + + final var testRecordAttributes = new HashSet(); + final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); + final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); + testRecordAttributes.add(recordNameAttribute); + testRecordAttributes.add(recordIdAttribute); + final var testRecordAttribute = new ObjectAttribute("recordStub", false, Set.of(new EncryptedQualifier()), + testRecordAttributes); + LeiaTestUtils.assertEquals(testRecordAttribute, LeiaTestUtils.filter(schemaAttributes, "recordStub").orElse(null)); + + final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), + TestEnum.TWO.name())); + LeiaTestUtils.assertEquals(enumClassAttribute, LeiaTestUtils.filter(schemaAttributes, "enumClass").orElse(null)); + + final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); + LeiaTestUtils.assertEquals(phoneNoAttribute, LeiaTestUtils.filter(schemaAttributes, "phoneNumber").orElse(null)); + } + + @Test + void testSchemaAttributes_WithParameterizedType() { + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestParameterizedStub.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(3, schemaAttributes.size()); + + final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), + new StringAttribute("element", false, new HashSet<>())); + LeiaTestUtils.assertEquals(valuesAttributes, LeiaTestUtils.filter(schemaAttributes, "values").orElse(null)); + + final var testPIIDataAttributes = new HashSet(); + final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); + final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); + testPIIDataAttributes.add(piiNameAttribute); + testPIIDataAttributes.add(accountNumberAttribute); + final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), + new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); + LeiaTestUtils.assertEquals(piiDataListAttribute, LeiaTestUtils.filter(schemaAttributes, "piiDataList").orElse(null)); + + final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), + new EnumAttribute("key", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), TestEnum.TWO.name())), + new StringAttribute("value", false, new HashSet<>())); + LeiaTestUtils.assertEquals(mapAttribute, LeiaTestUtils.filter(schemaAttributes, "map").orElse(null)); + } + + @Test + void testSchemaAttributes_WithRawCollections() { + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestRawCollectionStub.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(6, schemaAttributes.size()); + + final var rawListAttribute = new ArrayAttribute("rawList", false, Set.of(), null); + LeiaTestUtils.assertEquals(rawListAttribute, LeiaTestUtils.filter(schemaAttributes, "rawList").orElse(null)); + + final var rawLinkedListAttribute = new ArrayAttribute("rawLinkedList", false, Set.of(), null); + LeiaTestUtils.assertEquals(rawLinkedListAttribute, LeiaTestUtils.filter(schemaAttributes, "rawLinkedList").orElse(null)); + + final var rawSetAttribute = new ArrayAttribute("rawSet", false, Set.of(), null); + LeiaTestUtils.assertEquals(rawSetAttribute, LeiaTestUtils.filter(schemaAttributes, "rawSet").orElse(null)); + + final var rawHashSetAttribute = new ArrayAttribute("rawHashSet", false, Set.of(), null); + LeiaTestUtils.assertEquals(rawHashSetAttribute, LeiaTestUtils.filter(schemaAttributes, "rawHashSet").orElse(null)); + + final var rawMapAttribute = new MapAttribute("rawMap", false, Set.of(), null, null); + LeiaTestUtils.assertEquals(rawMapAttribute, LeiaTestUtils.filter(schemaAttributes, "rawMap").orElse(null)); + + final var rawSortedMapAttribute = new MapAttribute("rawSortedMap", false, Set.of(), null, null); + LeiaTestUtils.assertEquals(rawSortedMapAttribute, LeiaTestUtils.filter(schemaAttributes, "rawSortedMap").orElse(null)); + } + + @Test + void testSchemaAttributes_WithObjects() { + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestObjectStub.class); + Assertions.assertNotNull(schemaAttributes); + Assertions.assertEquals(5, schemaAttributes.size()); + + final var objectAttribute = new ObjectAttribute("object", false, Set.of(), null); + LeiaTestUtils.assertEquals(objectAttribute, LeiaTestUtils.filter(schemaAttributes, "object").orElse(null)); + + final var objectsAttribute = new ArrayAttribute("objects", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + LeiaTestUtils.assertEquals(objectsAttribute, LeiaTestUtils.filter(schemaAttributes, "objects").orElse(null)); + + final var objectListAttribute = new ArrayAttribute("objectList", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + LeiaTestUtils.assertEquals(objectListAttribute, LeiaTestUtils.filter(schemaAttributes, "objectList").orElse(null)); + + final var objectSetAttribute = new ArrayAttribute("objectSet", false, Set.of(), + new ObjectAttribute("element", false, Set.of(), null)); + LeiaTestUtils.assertEquals(objectSetAttribute, LeiaTestUtils.filter(schemaAttributes, "objectSet").orElse(null)); + + final var objectMapAttribute = new MapAttribute("objectMap", false, Set.of(), + new StringAttribute("key", false, Set.of()), + new ObjectAttribute("value", false, Set.of(), null)); + LeiaTestUtils.assertEquals(objectMapAttribute, LeiaTestUtils.filter(schemaAttributes, "objectMap").orElse(null)); + + } + + static class PrimitiveTestClass { + @Optional + String name; + int id; + } + @SchemaDefinition( name = TestRecord.NAME, namespace = TestRecord.NAMESPACE, diff --git a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java b/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java deleted file mode 100644 index 401df0c..0000000 --- a/leia-common/src/test/java/com/grookage/leia/common/utils/SchemaAttributeUtilsTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.grookage.leia.common.utils; - -import com.grookage.leia.common.TestUtils; -import com.grookage.leia.common.stubs.NestedStub; -import com.grookage.leia.common.stubs.RecordStub; -import com.grookage.leia.common.stubs.TestEnum; -import com.grookage.leia.common.stubs.TestObjectStub; -import com.grookage.leia.common.stubs.TestParameterizedStub; -import com.grookage.leia.common.stubs.TestRawCollectionStub; -import com.grookage.leia.models.annotations.attribute.Optional; -import com.grookage.leia.models.attributes.ArrayAttribute; -import com.grookage.leia.models.attributes.EnumAttribute; -import com.grookage.leia.models.attributes.IntegerAttribute; -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.StringAttribute; -import com.grookage.leia.models.qualifiers.EncryptedQualifier; -import com.grookage.leia.models.qualifiers.PIIQualifier; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.HashSet; -import java.util.Set; - -class SchemaAttributeUtilsTest { - - @Test - void testSchemaAttributes_WithPrimitiveClass() { - final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(PrimitiveTestClass.class); - Assertions.assertNotNull(schemaAttributeSet); - Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", true, new HashSet<>()); - TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributeSet, "id").orElse(null)); - } - - @Test - void testSchemaAttributes_WithRecordClass() { - final var schemaAttributeSet = SchemaAttributeUtils.getSchemaAttributes(RecordStub.class); - Assertions.assertNotNull(schemaAttributeSet); - Assertions.assertEquals(2, schemaAttributeSet.size()); - final var nameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); - TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributeSet, "name").orElse(null)); - final var idAttribute = new IntegerAttribute("id", true, new HashSet<>()); - TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributeSet, "id").orElse(null)); - } - - @Test - void testSchemaAttributes_WithNestedObject() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(NestedStub.class); - Assertions.assertFalse(schemaAttributes.isEmpty()); - Assertions.assertEquals(6, schemaAttributes.size()); - final var nameAttribute = new StringAttribute("name", false, new HashSet<>()); - TestUtils.assertEquals(nameAttribute, TestUtils.filter(schemaAttributes, "name").orElse(null)); - - final var idAttribute = new IntegerAttribute("id", false, new HashSet<>()); - TestUtils.assertEquals(idAttribute, TestUtils.filter(schemaAttributes, "id").orElse(null)); - - final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); - testPIIDataAttributes.add(piiNameAttribute); - testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataAttribute = new ObjectAttribute("piiData", false, Set.of(new PIIQualifier(), new EncryptedQualifier()), testPIIDataAttributes); - TestUtils.assertEquals(piiDataAttribute, TestUtils.filter(schemaAttributes, "piiData").orElse(null)); - - final var testRecordAttributes = new HashSet(); - final var recordNameAttribute = new StringAttribute("name", false, Set.of(new PIIQualifier())); - final var recordIdAttribute = new IntegerAttribute("id", true, new HashSet<>()); - testRecordAttributes.add(recordNameAttribute); - testRecordAttributes.add(recordIdAttribute); - final var testRecordAttribute = new ObjectAttribute("recordStub", false, Set.of(new EncryptedQualifier()), - testRecordAttributes); - TestUtils.assertEquals(testRecordAttribute, TestUtils.filter(schemaAttributes, "recordStub").orElse(null)); - - final var enumClassAttribute = new EnumAttribute("enumClass", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), - TestEnum.TWO.name())); - TestUtils.assertEquals(enumClassAttribute, TestUtils.filter(schemaAttributes, "enumClass").orElse(null)); - - final var phoneNoAttribute = new StringAttribute("phoneNumber", false, Set.of(new PIIQualifier())); - TestUtils.assertEquals(phoneNoAttribute, TestUtils.filter(schemaAttributes, "phoneNumber").orElse(null)); - } - - @Test - void testSchemaAttributes_WithParameterizedType() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestParameterizedStub.class); - Assertions.assertNotNull(schemaAttributes); - Assertions.assertEquals(3, schemaAttributes.size()); - - final var valuesAttributes = new ArrayAttribute("values", false, new HashSet<>(), - new StringAttribute("element", false, new HashSet<>())); - TestUtils.assertEquals(valuesAttributes, TestUtils.filter(schemaAttributes, "values").orElse(null)); - - final var testPIIDataAttributes = new HashSet(); - final var piiNameAttribute = new StringAttribute("name", false, new HashSet<>()); - final var accountNumberAttribute = new StringAttribute("accountNumber", false, Set.of(new EncryptedQualifier())); - testPIIDataAttributes.add(piiNameAttribute); - testPIIDataAttributes.add(accountNumberAttribute); - final var piiDataListAttribute = new ArrayAttribute("piiDataList", false, Set.of(new PIIQualifier()), - new ObjectAttribute("element", false, Set.of(new PIIQualifier()), testPIIDataAttributes)); - TestUtils.assertEquals(piiDataListAttribute, TestUtils.filter(schemaAttributes, "piiDataList").orElse(null)); - - final var mapAttribute = new MapAttribute("map", false, Set.of(new EncryptedQualifier()), - new EnumAttribute("key", false, new HashSet<>(), Set.of(TestEnum.ONE.name(), TestEnum.TWO.name())), - new StringAttribute("value", false, new HashSet<>())); - TestUtils.assertEquals(mapAttribute, TestUtils.filter(schemaAttributes, "map").orElse(null)); - } - - @Test - void testSchemaAttributes_WithRawCollections() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRawCollectionStub.class); - Assertions.assertNotNull(schemaAttributes); - Assertions.assertEquals(6, schemaAttributes.size()); - - final var rawListAttribute = new ArrayAttribute("rawList", false, Set.of(), null); - TestUtils.assertEquals(rawListAttribute, TestUtils.filter(schemaAttributes, "rawList").orElse(null)); - - final var rawLinkedListAttribute = new ArrayAttribute("rawLinkedList", false, Set.of(), null); - TestUtils.assertEquals(rawLinkedListAttribute, TestUtils.filter(schemaAttributes, "rawLinkedList").orElse(null)); - - final var rawSetAttribute = new ArrayAttribute("rawSet", false, Set.of(), null); - TestUtils.assertEquals(rawSetAttribute, TestUtils.filter(schemaAttributes, "rawSet").orElse(null)); - - final var rawHashSetAttribute = new ArrayAttribute("rawHashSet", false, Set.of(), null); - TestUtils.assertEquals(rawHashSetAttribute, TestUtils.filter(schemaAttributes, "rawHashSet").orElse(null)); - - final var rawMapAttribute = new MapAttribute("rawMap", false, Set.of(), null, null); - TestUtils.assertEquals(rawMapAttribute, TestUtils.filter(schemaAttributes, "rawMap").orElse(null)); - - final var rawSortedMapAttribute = new MapAttribute("rawSortedMap", false, Set.of(), null, null); - TestUtils.assertEquals(rawSortedMapAttribute, TestUtils.filter(schemaAttributes, "rawSortedMap").orElse(null)); - } - - @Test - void testSchemaAttributes_WithObjects() { - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestObjectStub.class); - Assertions.assertNotNull(schemaAttributes); - Assertions.assertEquals(5, schemaAttributes.size()); - - final var objectAttribute = new ObjectAttribute("object", false, Set.of(), null); - TestUtils.assertEquals(objectAttribute, TestUtils.filter(schemaAttributes, "object").orElse(null)); - - final var objectsAttribute = new ArrayAttribute("objects", false, Set.of(), - new ObjectAttribute("element", false, Set.of(), null)); - TestUtils.assertEquals(objectsAttribute, TestUtils.filter(schemaAttributes, "objects").orElse(null)); - - final var objectListAttribute = new ArrayAttribute("objectList", false, Set.of(), - new ObjectAttribute("element", false, Set.of(), null)); - TestUtils.assertEquals(objectListAttribute, TestUtils.filter(schemaAttributes, "objectList").orElse(null)); - - final var objectSetAttribute = new ArrayAttribute("objectSet", false, Set.of(), - new ObjectAttribute("element", false, Set.of(), null)); - TestUtils.assertEquals(objectSetAttribute, TestUtils.filter(schemaAttributes, "objectSet").orElse(null)); - - final var objectMapAttribute = new MapAttribute("objectMap", false, Set.of(), - new StringAttribute("key", false, Set.of()), - new ObjectAttribute("value", false, Set.of(), null)); - TestUtils.assertEquals(objectMapAttribute, TestUtils.filter(schemaAttributes, "objectMap").orElse(null)); - - } - - static class PrimitiveTestClass { - @Optional - String name; - int id; - } - -} \ No newline at end of file diff --git a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java index ccd57db..67cfd92 100644 --- a/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java +++ b/leia-common/src/test/java/com/grookage/leia/common/validation/SchemaPayloadValidatorTest.java @@ -1,10 +1,10 @@ package com.grookage.leia.common.validation; +import com.grookage.leia.common.builder.SchemaBuilder; import com.grookage.leia.common.stubs.NestedStub; import com.grookage.leia.common.stubs.TestObjectStub; import com.grookage.leia.common.stubs.TestParameterizedStub; import com.grookage.leia.common.stubs.TestRawCollectionStub; -import com.grookage.leia.common.utils.SchemaAttributeUtils; import com.grookage.leia.models.ResourceHelper; import com.grookage.leia.models.attributes.ArrayAttribute; import com.grookage.leia.models.attributes.BooleanAttribute; @@ -210,7 +210,7 @@ void testInvalidMapValueType() throws Exception { void testValidateNested() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validNestedStub.json", NestedStub.class)); - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(NestedStub.class); + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(NestedStub.class); final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -220,7 +220,7 @@ void testValidateNested() { void testValidateParameterizedStub() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validParameterizedStub.json", TestParameterizedStub.class)); - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestParameterizedStub.class); + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestParameterizedStub.class); final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); assertTrue(errors.isEmpty()); } @@ -230,7 +230,7 @@ void testValidateParameterizedStub() { void testObjectValidation() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validObjectStub.json", TestObjectStub.class)); - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestObjectStub.class); + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestObjectStub.class); final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); Assertions.assertTrue(errors.isEmpty()); } @@ -240,7 +240,7 @@ void testObjectValidation() { void testRawCollectionSchemaValidation() { final var jsonNode = ResourceHelper.getObjectMapper().valueToTree(ResourceHelper.getResource("stubs/validRawCollectionStub.json", TestRawCollectionStub.class)); - final var schemaAttributes = SchemaAttributeUtils.getSchemaAttributes(TestRawCollectionStub.class); + final var schemaAttributes = SchemaBuilder.getSchemaAttributes(TestRawCollectionStub.class); final var errors = SchemaPayloadValidator.validate(jsonNode, SchemaValidationType.STRICT, schemaAttributes); Assertions.assertTrue(errors.isEmpty()); }