diff --git a/build.gradle b/build.gradle index d74ac25c2..91e0967e6 100644 --- a/build.gradle +++ b/build.gradle @@ -73,13 +73,13 @@ subprojects { compile project.deps.jackson.databind compile project.deps.logs.slf4j compile project.deps.logs.logback + compile project.deps.tests.jsonAssert compileOnly project.deps.vertx.codegen compileOnly project.deps.lombok annotationProcessor project.deps.lombok testCompile project.deps.tests.junit - testCompile project.deps.tests.jsonAssert testCompile project.deps.vertx.junit testCompileOnly project.deps.lombok testAnnotationProcessor project.deps.lombok diff --git a/core/base/src/main/java/com/nubeiot/core/exceptions/ValidationError.java b/core/base/src/main/java/com/nubeiot/core/exceptions/ValidationError.java new file mode 100644 index 000000000..a5e4abebe --- /dev/null +++ b/core/base/src/main/java/com/nubeiot/core/exceptions/ValidationError.java @@ -0,0 +1,27 @@ +package com.nubeiot.core.exceptions; + +import com.nubeiot.core.utils.Strings; + +import lombok.Builder; + +@Builder(builderClassName = "Builder") +public class ValidationError { + + static final NubeException.ErrorCode DEFAULT_ERROR_CODE = NubeException.ErrorCode.INVALID_ARGUMENT; + static final String DEFAULT_ERROR_TYPE = "ValidationError"; + + @lombok.Builder.Default + private NubeException.ErrorCode errorCode = DEFAULT_ERROR_CODE; + @lombok.Builder.Default + private String errorType = DEFAULT_ERROR_TYPE; + @lombok.Builder.Default + private String value = ""; + private String message; + + public NubeException execute() { + String errorMessage = (((Strings.isNotBlank(errorType) ? errorType + ": " : "") + value).trim() + " " + message) + .trim(); + return new NubeException(errorCode, errorMessage); + } + +} diff --git a/core/base/src/main/java/com/nubeiot/core/utils/JsonUtils.java b/core/base/src/main/java/com/nubeiot/core/utils/JsonUtils.java new file mode 100644 index 000000000..d45ff5c43 --- /dev/null +++ b/core/base/src/main/java/com/nubeiot/core/utils/JsonUtils.java @@ -0,0 +1,121 @@ +package com.nubeiot.core.utils; + +import java.util.Map; +import java.util.function.BiFunction; + +import org.skyscreamer.jsonassert.JSONCompare; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.skyscreamer.jsonassert.JSONCompareResult; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class JsonUtils { + + /** + * JsonObject @source gets merge with the JsonObject @defaultValue. + */ + public static BiFunction mergeJsonObjectFunc = (source, defaultValue) -> { + ((JsonObject) defaultValue).forEach( + (defaultValueMap) -> iterateMergeFunction((JsonObject) source, defaultValueMap)); + return source; + }; + + public static Object getObject(JsonObject s, String field, Object defaultValue) { + Object output = getObject(s, field); + if (output == null) { + output = defaultValue; + } + return output; + } + + public static Object getObject(JsonObject s, String field) { + String[] fields; + if (Strings.isNotBlank(field)) { + fields = field.split("\\."); + } else { + return s; + } + + Object output = null; + + try { + for (int i = 0; i < fields.length - 1; i++) { + s = s.getJsonObject(fields[i]); + } + output = s.getValue(fields[fields.length - 1]); + } catch (NullPointerException | ClassCastException ignored) { + } + return output; + } + + public static boolean compareJsonObject(JsonObject expected, JsonObject actual) { + if (expected == null && actual == null) { + return true; + } else if (expected == null || actual == null) { + return false; + } + + JSONCompareResult result = null; + try { + result = JSONCompare.compareJSON(expected.encode(), actual.encode(), JSONCompareMode.STRICT); + } catch (org.json.JSONException e) { + e.printStackTrace(); + } + return result != null && result.passed(); + } + + private static void iterateMergeFunction(JsonObject source, Map.Entry defaultValueMap) { + Object defaultValue = defaultValueMap.getValue(); + String defaultKey = defaultValueMap.getKey(); + JsonObject sourceObject = source; + Object sourceValue = sourceObject.getValue(defaultKey); + if (defaultValue instanceof JsonObject && (sourceValue instanceof JsonObject || sourceValue == null)) { + if (sourceValue == null) { + sourceObject.put(defaultKey, new JsonObject()); + } + sourceObject = sourceObject.getJsonObject(defaultKey); + for (Map.Entry defaultValueMapChild : (JsonObject) defaultValue) { + iterateMergeFunction(sourceObject, defaultValueMapChild); + } + } else if (defaultValue instanceof JsonArray && (sourceValue instanceof JsonArray || sourceValue == null)) { + if (sourceValue == null) { + sourceObject.put(defaultKey, new JsonArray()); + } + JsonArray sourceArray = sourceObject.getJsonArray(defaultKey); + iterateJsonArrayMergeFunction(sourceArray, (JsonArray) defaultValue); + } else if (!(defaultValue instanceof Iterable) && !(sourceValue instanceof Iterable)) { + if (sourceValue == null) { + sourceObject.put(defaultKey, defaultValue); + } + } + } + + private static void iterateJsonArrayMergeFunction(JsonArray sourceArray, JsonArray defaultValueArray) { + Object defaultValue = defaultValueArray.getValue(0); + if (defaultValue instanceof JsonObject) { + if (sourceArray.size() == 0) { + sourceArray.add(new JsonObject()); + } + for (Object o : sourceArray) { + ((JsonObject) defaultValue).forEach( + defaultValueMapChild -> iterateMergeFunction((JsonObject) o, defaultValueMapChild)); + } + } else if (defaultValue instanceof JsonArray) { + if (sourceArray.size() == 0) { + sourceArray.add(new JsonArray()); + } + for (Object o : sourceArray) { + if (o instanceof JsonArray) { + iterateJsonArrayMergeFunction((JsonArray) o, (JsonArray) defaultValue); + } + } + } else { + if (sourceArray.size() == 0) { + sourceArray.add(defaultValue); + } + } + } + +} + diff --git a/core/base/src/test/java/com/nubeiot/core/utils/JsonUtilsTest.java b/core/base/src/test/java/com/nubeiot/core/utils/JsonUtilsTest.java new file mode 100644 index 000000000..876690388 --- /dev/null +++ b/core/base/src/test/java/com/nubeiot/core/utils/JsonUtilsTest.java @@ -0,0 +1,113 @@ +package com.nubeiot.core.utils; + +import org.json.JSONException; +import org.junit.Assert; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class JsonUtilsTest { + + @Test + public void test_get_json_value() { + JsonObject jsonObject = new JsonObject().put("details", new JsonObject().put("first_name", "Shane") + .put("last_name", "Watson") + .put("club", new JsonArray().add( + "Sydney Sixers").add("RR"))); + Assert.assertEquals(JsonUtils.getObject(jsonObject, "details").toString(), + "{\"first_name\":\"Shane\"," + "\"last_name\":\"Watson\",\"club\":[\"Sydney " + + "Sixers\",\"RR\"]}"); + Assert.assertEquals(JsonUtils.getObject(jsonObject, "details.first_name"), "Shane"); + Assert.assertEquals(JsonUtils.getObject(jsonObject, "details.last_name"), "Watson"); + Assert.assertEquals(JsonUtils.getObject(jsonObject, "details.club").toString(), "[\"Sydney Sixers\",\"RR\"]"); + + Assert.assertNull(JsonUtils.getObject(jsonObject, "details.last_name.club")); + } + + @Test + public void test_compareJsonObject_success() { + JsonObject actual = new JsonObject().put("details", new JsonObject().put("first_name", "Shane")); + JsonObject expected = new JsonObject().put("details", new JsonObject().put("first_name", "Shane")); + + Assert.assertTrue(JsonUtils.compareJsonObject(expected, actual)); + Assert.assertTrue(JsonUtils.compareJsonObject(null, null)); + } + + @Test + public void test_compareJsonObject_failure() { + JsonObject actual = new JsonObject().put("details", new JsonObject().put("first_name", "Shane")); + JsonObject expected = new JsonObject().put("details", new JsonObject()); + + Assert.assertFalse(JsonUtils.compareJsonObject(expected, actual)); + Assert.assertFalse(JsonUtils.compareJsonObject(expected, null)); + Assert.assertFalse(JsonUtils.compareJsonObject(null, actual)); + } + + @Test + public void test_mergeJsonObjectFunc1() throws JSONException { + JsonObject sourceJsonObject = new JsonObject().put("a", "10").put("d", new JsonArray()); + JsonObject defaultJsonObject = new JsonObject().put("b", 20) + .put("c", new JsonObject().put("x", "2")) + .put("d", new JsonArray().add(new JsonObject().put("y", 3))); + + JsonUtils.mergeJsonObjectFunc.apply(sourceJsonObject, defaultJsonObject); + System.out.println(sourceJsonObject); + JSONAssert.assertEquals(sourceJsonObject.encode(), + "{\"a\":\"10\",\"d\":[{\"y\":3}],\"b\":20," + "\"c\":{\"x\":\"2\"}}", true); + } + + @Test + public void test_mergeJsonObjectFunc2() throws JSONException { + JsonObject sourceJsonObject = new JsonObject().put("a", "10") + .put("d", new JsonArray().add(new JsonObject().put("z", 3))); + JsonObject defaultJsonObject = new JsonObject().put("b", 20) + .put("c", new JsonObject().put("x", "2")) + .put("d", new JsonArray().add(new JsonObject().put("y", 3))); + + JsonUtils.mergeJsonObjectFunc.apply(sourceJsonObject, defaultJsonObject); + System.out.println(sourceJsonObject); + JSONAssert.assertEquals(sourceJsonObject.encode(), + "{\"a\":\"10\",\"d\":[{\"z\":3,\"y\":3}],\"b\":20,\"c\":{\"x\":\"2\"}}", true); + } + + @Test + public void test_mergeJsonObjectFunc3() throws JSONException { + JsonObject sourceJsonObject = new JsonObject().put("a", "10").put("d", new JsonArray()); + JsonObject defaultJsonObject = new JsonObject().put("b", 20) + .put("c", new JsonObject().put("x", "2")) + .put("d", new JsonArray().add(new JsonArray().add("Hello"))); + + JsonUtils.mergeJsonObjectFunc.apply(sourceJsonObject, defaultJsonObject); + System.out.println(sourceJsonObject); + JSONAssert.assertEquals(sourceJsonObject.encode(), + "{\"a\":\"10\",\"d\":[[\"Hello\"]],\"b\":20,\"c\":{\"x\":\"2\"}}", true); + } + + @Test + public void test_mergeJsonObjectFunc4() throws JSONException { + JsonObject sourceJsonObject = new JsonObject().put("a", "10"); + JsonObject defaultJsonObject = new JsonObject().put("b", 20) + .put("c", new JsonObject().put("x", "2")) + .put("d", new JsonArray().add("y")); + + JsonUtils.mergeJsonObjectFunc.apply(sourceJsonObject, defaultJsonObject); + System.out.println(sourceJsonObject); + JSONAssert.assertEquals(sourceJsonObject.encode(), "{\"a\":\"10\",\"b\":20,\"c\":{\"x\":\"2\"},\"d\":[\"y\"]}", + true); + } + + @Test + public void test_mergeJsonObjectFunc5() throws JSONException { + JsonObject sourceJsonObject = new JsonObject().put("d", + new JsonArray().add(new JsonObject()).add(new JsonObject())); + JsonObject defaultJsonObject = new JsonObject().put("d", new JsonArray().add( + new JsonObject().put("x", new JsonArray().add("2")))); + + JsonUtils.mergeJsonObjectFunc.apply(sourceJsonObject, defaultJsonObject); + System.out.println(sourceJsonObject); + JSONAssert.assertEquals(sourceJsonObject.encode(), "{\"d\":[{\"x\":[\"2\"]},{\"x\":[\"2\"]}]}", true); + } + +} diff --git a/core/validator/build.gradle b/core/validator/build.gradle new file mode 100644 index 000000000..9ded852ab --- /dev/null +++ b/core/validator/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile project(':core:base') + + testCompile project(':core:base').sourceSets.test.output +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/DataTypeValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/DataTypeValidation.java new file mode 100644 index 000000000..0e7a0fdd9 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/DataTypeValidation.java @@ -0,0 +1,20 @@ +package com.nubeiot.core.validator; + +import com.nubeiot.core.exceptions.ValidationError; + +public abstract class DataTypeValidation implements Validation { + + protected abstract Class classType(); + + @Override + public ValidationResult validity(T s) { + if (classType().isInstance(s)) { + return ValidationResult.valid(); + } + + return ValidationResult.invalid(ValidationError.builder() + .value(s == null ? null : s.toString()) + .message("is not the type of " + classType().getName())); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidation.java new file mode 100644 index 000000000..e5335e34a --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidation.java @@ -0,0 +1,21 @@ +package com.nubeiot.core.validator; + +import com.nubeiot.core.utils.JsonUtils; +import com.nubeiot.core.utils.Strings; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public interface JsonValidation { + + default Object get(Object data, String key) { + if (Strings.isBlank(key) || data instanceof JsonArray) { + return data; + } + if (!(data instanceof JsonObject)) { + return null; + } + return JsonUtils.getObject((JsonObject) data, key); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationHolder.java b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationHolder.java new file mode 100644 index 000000000..1b92ead62 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationHolder.java @@ -0,0 +1,36 @@ +package com.nubeiot.core.validator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.NonNull; + +public class JsonValidationHolder implements JsonValidation, ValidationHolder { + + protected Map>> validations = new HashMap<>(); + + @Override + public Map>> validations() { + return this.validations; + } + + @Override + public ValidationHolder add(String field, @NonNull Validation validation) { + List> validationList = this.validations.get(field); + if (validationList == null) { + validationList = new ArrayList<>(); + } + validationList.add(validation); + this.validations.put(field, validationList); + return this; + } + + @Override + public Object get(Object data, String key) { + return JsonValidation.super.get(data, key); + } + +} + diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationLoop.java b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationLoop.java new file mode 100644 index 000000000..d67706b9d --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationLoop.java @@ -0,0 +1,85 @@ +package com.nubeiot.core.validator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.utils.Strings; +import com.nubeiot.core.validator.utils.ValidationUtils; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class JsonValidationLoop extends JsonValidationHolder { + + protected final String parentField; + private List> jsonValidationLoops = new ArrayList<>(); + private String previousParentField; + + public void add(@NonNull JsonValidationLoop validation) { + jsonValidationLoops.add(validation); + } + + @Override + public ValidationResult validate(T data) { + return validate(this, data); + } + + protected ValidationResult validate(JsonValidationLoop looper, T data) { + if (nullable() && data == null) { + return ValidationResult.valid(); + } + + List validationResults = new ArrayList<>(); + + Object value = this.get(data, looper.parentField); + if (!(value instanceof JsonArray || value instanceof JsonObject)) { + return ValidationResult.invalid(ValidationError.builder() + .value(looper.parentField) + .message("is not the type of JsonArray || JsonObject")); + } + + Iterable v = (Iterable) value; + int i = 0; + for (final Object o : v) { + Object loopedValue; + String actualParentField = + (Strings.isNotBlank(looper.previousParentField) ? looper.previousParentField + "." : "") + + (Strings.isNotBlank(looper.parentField) ? looper.parentField + "." : ""); + + if (o instanceof Map.Entry) { + Map.Entry keyValuePair = (Map.Entry) o; + actualParentField += keyValuePair.getKey(); + loopedValue = keyValuePair.getValue(); + } else { + actualParentField += "[" + i + "]"; + i++; + loopedValue = o; + } + + for (Map.Entry>> entry : looper.validations.entrySet()) { + String key = entry.getKey(); + List> innerValidations = entry.getValue(); + Object validateValue = get(loopedValue, key); + for (Validation validation : innerValidations) { + String actualField = Strings.isNotBlank(key) ? actualParentField + "." + key : actualParentField; + validationResults.add( + ValidationUtils.transferValidationInputToFieldFunc.apply(validation.validate(validateValue), + actualField)); + } + } + + for (JsonValidationLoop validation : looper.jsonValidationLoops) { + validation.previousParentField = actualParentField; + validationResults.add(validation.validate(validation, this.get(loopedValue, validation.parentField))); + } + } + + return ValidationUtils.mergeValidationResultsFunc.apply(validationResults); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationWhen.java b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationWhen.java new file mode 100644 index 000000000..27fec3196 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/JsonValidationWhen.java @@ -0,0 +1,83 @@ +package com.nubeiot.core.validator; + +import java.util.HashSet; +import java.util.Set; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.utils.Strings; +import com.nubeiot.core.validator.utils.ValidationUtils; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class JsonValidationWhen implements JsonValidation, Validation { + + private String isField; + + private Validation is; + private Validation then; + private Validation otherwise; + + private Set inputs = new HashSet<>(); + + private String field; + + @Override + public ValidationResult validity(T s) { + return this.validate(this, s); + } + + protected ValidationResult validate(JsonValidationWhen jsonValidationWhen, T s) { + if (jsonValidationWhen.inputs.size() == 3 && + Strings.isNotBlank(field)) { // jsonValidationWhen all case are specified + + if (jsonValidationWhen.then instanceof JsonValidationWhen) { + return validate((JsonValidationWhen) jsonValidationWhen.then, s); + } else if (jsonValidationWhen.otherwise instanceof JsonValidationWhen) { + return validate((JsonValidationWhen) jsonValidationWhen.otherwise, s); + } + + Object v = get(s, jsonValidationWhen.isField); + if (jsonValidationWhen.is.validate(v).isValid()) { + v = get(s, field); + return ValidationUtils.transferValidationInputToFieldFunc.apply(jsonValidationWhen.then.validate(v), + field); + } else { + v = get(s, field); + return ValidationUtils.transferValidationInputToFieldFunc.apply( + jsonValidationWhen.otherwise.validate(v), field); + } + } else { + return ValidationResult.invalid(ValidationError.builder() + .errorCode(NubeException.ErrorCode.INITIALIZER_ERROR) + .message( + "JsonValidationWhen validation Initialization Error")); + } + } + + public JsonValidationWhen registerField(String field) { + this.field = field; + return this; + } + + public JsonValidationWhen registerIs(String field, Validation is) { + inputs.add(1); + this.isField = field; + this.is = is; + return this; + } + + public JsonValidationWhen registerThen(Validation then) { + inputs.add(2); + this.then = then; + return this; + } + + public JsonValidationWhen registerOtherwise(Validation otherwise) { + inputs.add(3); + this.otherwise = otherwise; + return this; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/Validation.java b/core/validator/src/main/java/com/nubeiot/core/validator/Validation.java new file mode 100644 index 000000000..8e091c8a1 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/Validation.java @@ -0,0 +1,18 @@ +package com.nubeiot.core.validator; + +public interface Validation { + + default ValidationResult validate(T data) { + if (nullable() && data == null) { + return ValidationResult.valid(); + } + return validity(data); + } + + ValidationResult validity(T data); + + default boolean nullable() { + return true; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/ValidationHolder.java b/core/validator/src/main/java/com/nubeiot/core/validator/ValidationHolder.java new file mode 100644 index 000000000..02bd8e14d --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/ValidationHolder.java @@ -0,0 +1,63 @@ +package com.nubeiot.core.validator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.nubeiot.core.validator.utils.ValidationUtils; + +import lombok.NonNull; + +/** + * For map, json object, or POJO + * + * @param + */ +public interface ValidationHolder extends Validation { + + Map>> validations(); + + ValidationHolder add(String field, @NonNull Validation validation); + + Object get(Object data, String field); + + default ValidationHolder add(String field, Validation... validations) { + Arrays.stream(validations).filter(Objects::nonNull).forEach(validation -> add(field, validation)); + return this; + } + + @Override + default ValidationResult validate(T data) { + final List validationResults = new ArrayList<>(); + validations().forEach((field, validations) -> { + Object v = get(data, field); + validations.forEach(validation -> validationResults.add( + ValidationUtils.transferValidationInputToFieldFunc.apply(validation.validate(v), field))); + }); + return ValidationUtils.mergeValidationResultsFunc.apply(validationResults); + } + + @Override + default ValidationResult validity(T data) { + return null; + } + + // todo: we will work on this + default ValidationResult eagerValidate(T data) { + for (Map.Entry>> entry : validations().entrySet()) { + String field = entry.getKey(); + List> validations = entry.getValue(); + Object v = get(data, field); + for (Validation validation : validations) { + ValidationResult validationResult = validation.validate(v); + if (!validationResult.errors().isEmpty()) { + return ValidationUtils.transferValidationInputToFieldFunc.apply(validationResult, field); + } + } + } + return ValidationResult.valid(); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/ValidationResult.java b/core/validator/src/main/java/com/nubeiot/core/validator/ValidationResult.java new file mode 100644 index 000000000..863204ef8 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/ValidationResult.java @@ -0,0 +1,40 @@ +package com.nubeiot.core.validator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.nubeiot.core.exceptions.ValidationError; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ValidationResult { + + List errors = new ArrayList<>(); + + public static ValidationResult valid() { + return new ValidationResult(); + } + + public static ValidationResult invalid(List validationErrorBuilders) { + return new ValidationResult(validationErrorBuilders); + } + + public static ValidationResult invalid(ValidationError.Builder validationErrorBuilder) { + return invalid(Collections.singletonList(validationErrorBuilder)); + } + + public boolean isValid() { + return errors().isEmpty(); + } + + public List errors() { + return errors; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/Validator.java b/core/validator/src/main/java/com/nubeiot/core/validator/Validator.java new file mode 100644 index 000000000..d3a34a8cf --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/Validator.java @@ -0,0 +1,69 @@ +package com.nubeiot.core.validator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +import com.nubeiot.core.exceptions.ValidationError; + +import lombok.Builder; +import lombok.Builder.Default; +import lombok.NonNull; +import lombok.Singular; + +@Builder(builderClassName = "Builder") +public class Validator { + + private T defaultValue; + @Default + private BiFunction mergeFunc = (o1, o2) -> Objects.isNull(o1) ? o2 : o1; + @Default + private boolean nullable = true; + @NonNull + @Singular + private List> validations; + + /** + * Validate data to the end then bundle all errors in {@link ValidationResult} + * + * @param data + * @return + */ + public ValidationResult execute(T data) { + T merged = before(data); + List errors = new ArrayList<>(); + for (Validation validation : validations) { + ValidationResult validationResult = validation.validate(merged); + if (!validationResult.errors().isEmpty()) { + errors.addAll(validationResult.errors()); + } + } + return new ValidationResult(errors); + } + + /** + * Validate data and raise error immediately if encounter any error + * + * @param data + * @return + */ + public ValidationResult eagerExecute(T data) { + T merged = before(data); + for (Validation validation : validations) { + ValidationResult validationResult; + + validationResult = validation.validate(merged); + + if (!validationResult.errors().isEmpty()) { + return ValidationResult.invalid(validationResult.errors()); + } + } + return ValidationResult.valid(); + } + + private T before(T data) { + return mergeFunc.apply(data, defaultValue); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/proposal/DependencyValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/DependencyValidation.java new file mode 100644 index 000000000..719d66d80 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/DependencyValidation.java @@ -0,0 +1,39 @@ +package com.nubeiot.core.validator.proposal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.NonNull; + +public interface DependencyValidation extends ValidationHolder { + + /** + * Dependency validations + *

+ * It means after validating specified key then do validate for another + */ + //TODO: maybe need to detected Circular dependency + Map>> dependencies(); + + DependencyValidation add(@NonNull String field, @NonNull Validation validation, + @NonNull Map> depends); + + DependencyValidation add(@NonNull String field, @NonNull Validation validation, @NonNull String dependField, + Validation... dependValidations); + + @Override + default ValidationResult validate(T data) { + //Collect data + validations().forEach((key, validations) -> { + Object v = get(data, key); + validations.forEach(validation -> validation.validate(v)); + dependencies().getOrDefault(key, new HashMap<>()).forEach((k2, validations2) -> { + Object v2 = get(data, k2); + validations2.forEach(validation -> validation.validate(v2)); + }); + }); + return null; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validation.java b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validation.java new file mode 100644 index 000000000..a022e9491 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validation.java @@ -0,0 +1,10 @@ +package com.nubeiot.core.validator.proposal; + +/** + * @param + */ +public interface Validation { + + ValidationResult validate(T data); + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationHolder.java b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationHolder.java new file mode 100644 index 000000000..d4fc5602f --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationHolder.java @@ -0,0 +1,38 @@ +package com.nubeiot.core.validator.proposal; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import lombok.NonNull; + +/** + * For map, json object, or POJO + * + * @param + */ +public interface ValidationHolder extends Validation { + + Map> validations(); + + ValidationHolder add(String field, @NonNull Validation validation); + + Object get(T data, String key); + + default ValidationHolder add(String field, Validation... validations) { + Arrays.stream(validations).filter(Objects::nonNull).forEach(validation -> add(field, validation)); + return this; + } + + @Override + default ValidationResult validate(T data) { + //Collect data + validations().forEach((s, validations) -> { + Object v = get(data, s); + validations.forEach(validation -> validation.validate(v)); + }); + return null; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationResult.java b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationResult.java new file mode 100644 index 000000000..bffbfa5fb --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/ValidationResult.java @@ -0,0 +1,15 @@ +package com.nubeiot.core.validator.proposal; + +import java.util.List; + +public interface ValidationResult { + + T data(); + + default boolean isValid() { + return errors().isEmpty(); + } + + List errors(); + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validator.java b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validator.java new file mode 100644 index 000000000..03ba68e7e --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/proposal/Validator.java @@ -0,0 +1,58 @@ +package com.nubeiot.core.validator.proposal; + +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +import lombok.Builder; +import lombok.Builder.Default; +import lombok.NonNull; +import lombok.Singular; + +@Builder(builderClassName = "Builder") +public class Validator { + + private T defaultValue; + //TODO introduce some static BiFunction such as merge json + @Default + private BiFunction mergeFunc = (t, t2) -> Objects.isNull(t2) ? t : t2; + @Default + private boolean nullable = true; + @NonNull + @Singular + private List> validations; + + /** + * Validate data to the end then bundle all errors in {@link ValidationResult} + * + * @param data + * @return + */ + public ValidationResult execute(T data) { + T merged = before(data); + // TODO: Collect and merge all errors if any at here + validations.forEach(validation -> validation.validate(merged)); + return null; + } + + /** + * Validate data and raise error immediately if encounter any error + * + * @param data + * @return + */ + public ValidationResult eagerExecute(T data) { + T merged = before(data); + return null; + } + + private T before(T data) { + T d = mergeFunc.apply(defaultValue, data); + if (Objects.isNull(d) && !nullable) { + // or return ValidationResult + // throw new ValidationError("non null"); + } + return d; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/utils/ValidationUtils.java b/core/validator/src/main/java/com/nubeiot/core/validator/utils/ValidationUtils.java new file mode 100644 index 000000000..b1baaf5cc --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/utils/ValidationUtils.java @@ -0,0 +1,48 @@ +package com.nubeiot.core.validator.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.ValidationResult; + +public class ValidationUtils { + + public static Function, NubeException> convertValidationErrorsToException + = (List errorBuilders) -> { + StringBuilder message = new StringBuilder(); + NubeException.ErrorCode code = NubeException.ErrorCode.INVALID_ARGUMENT; + for (int i = 0; i < errorBuilders.size(); i++) { + NubeException nubeException = errorBuilders.get(i).build().execute(); + message.append(nubeException.getMessage()); + if (code != nubeException.getErrorCode()) { + code = nubeException.getErrorCode(); + } + if (i < errorBuilders.size() - 1) { + message.append(" && "); + } + } + return new NubeException(code, message.toString()); + }; + + public static BiFunction transferValidationInputToFieldFunc + = (validationResult, field) -> { + validationResult.errors().forEach(error -> error.value(field)); + return validationResult; + }; + + public static Function, ValidationResult> mergeValidationResultsFunc + = (validationResults) -> { + List validationErrors = new ArrayList<>(); + for (ValidationResult validationResult : validationResults) { + if (!validationResult.errors().isEmpty()) { + validationErrors.addAll(validationResult.errors()); + } + } + return new ValidationResult(validationErrors); + }; + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Alternative.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Alternative.java new file mode 100644 index 000000000..3c3fa23b6 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Alternative.java @@ -0,0 +1,56 @@ +package com.nubeiot.core.validator.validations; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; +import com.nubeiot.core.validator.utils.ValidationUtils; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Alternative implements Validation { + + private final List> validations; + private BiFunction alternativeValidationBuilderFunc + = (validationResult, value) -> { + StringBuilder message = new StringBuilder(); + NubeException.ErrorCode errorCode = NubeException.ErrorCode.INVALID_ARGUMENT; + for (int i = 0; i < validationResult.getErrors().size(); i++) { + ValidationError.Builder error = validationResult.getErrors().get(i); + // clearing errorType and value + NubeException nubeException = error.errorType("").value("").build().execute(); + message.append(nubeException.getMessage()); + if (i != validationResult.getErrors().size() - 1) { + message.append(" || "); + } + if (errorCode != nubeException.getErrorCode()) { + errorCode = nubeException.getErrorCode(); + } + } + return ValidationError.builder().value(value).message(message.toString()).errorCode(errorCode); + }; + + @Override + public ValidationResult validity(T s) { + List validationResults = new ArrayList<>(); + for (Validation validation : new HashSet<>(validations)) { + validationResults.add(validation.validate(s)); + } + + if (validationResults.stream().filter(x -> !x.isValid()).collect(Collectors.toList()).size() == + validations.size()) { + + return ValidationResult.invalid(alternativeValidationBuilderFunc.apply( + ValidationUtils.mergeValidationResultsFunc.apply(validationResults), s.toString())); + } + return ValidationResult.valid(); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Any.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Any.java new file mode 100644 index 000000000..3ccb94016 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Any.java @@ -0,0 +1,17 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +public class Any implements Validation { + + public ValidationResult validate() { + return this.validate(null); + } + + @Override + public ValidationResult validity(T s) { + return ValidationResult.valid(); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Composition.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Composition.java new file mode 100644 index 000000000..7f97a417a --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Composition.java @@ -0,0 +1,61 @@ +package com.nubeiot.core.validator.validations; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; +import com.nubeiot.core.validator.utils.ValidationUtils; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Composition implements Validation { + + private final List> validations; + private BiFunction compositionValidationBuilderFunc + = (validationResult, value) -> { + StringBuilder message = new StringBuilder(); + NubeException.ErrorCode errorCode = NubeException.ErrorCode.INVALID_ARGUMENT; + for (int i = 0; i < validationResult.getErrors().size(); i++) { + ValidationError.Builder error = validationResult.getErrors().get(i); + // clearing errorType and value + NubeException nubeException = error.errorType("").value("").build().execute(); + message.append(nubeException.getMessage()); + if (i != validationResult.getErrors().size() - 1) { + message.append(" && "); + } + if (errorCode != nubeException.getErrorCode()) { + errorCode = nubeException.getErrorCode(); + } + } + return ValidationError.builder().value(value).message(message.toString()).errorCode(errorCode); + }; + + @Override + public ValidationResult validity(T s) { + List validationResults = new ArrayList<>(); + for (Validation validation : validations) { + validationResults.add(validation.validate(s)); + } + + if (validationResults.stream().filter(ValidationResult::isValid).collect(Collectors.toList()).size() == + validations.size()) { + + return ValidationResult.valid(); + } + return ValidationResult.invalid( + compositionValidationBuilderFunc.apply(ValidationUtils.mergeValidationResultsFunc.apply(validationResults), + s == null ? null : s.toString())); + } + + @Override + public boolean nullable() { + return false; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Contains.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Contains.java new file mode 100644 index 000000000..bad8bd819 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Contains.java @@ -0,0 +1,49 @@ +package com.nubeiot.core.validator.validations; + +import java.util.Set; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +import io.vertx.core.json.JsonArray; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@AllArgsConstructor +public class Contains implements Validation { + + private final Set items; + private boolean strict = true; + + @Override + public ValidationResult validity(T s) { + boolean contains = false; + if (s instanceof JsonArray) { + for (Object o : (JsonArray) s) { + if (items.contains(o)) { + contains = true; + if (!strict) { + break; + } + } else { + contains = false; + if (strict) { + break; + } + } + } + } else { + contains = items.contains(s); + } + return contains ? ValidationResult.valid() : ValidationResult.invalid(buildErrorMessage(s)); + } + + private ValidationError.Builder buildErrorMessage(T s) { + return strict + ? ValidationError.builder().value(s.toString()).message("strictly should fall in the " + items) + : ValidationError.builder().value(s.toString()).message("should fall in the " + items); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Exist.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Exist.java new file mode 100644 index 000000000..0fc2e437a --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Exist.java @@ -0,0 +1,22 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +public class Exist implements Validation { + + @Override + public ValidationResult validity(T s) { + if (s != null) { + return ValidationResult.valid(); + } + return ValidationResult.invalid(ValidationError.builder().message("required value is null")); + } + + @Override + public boolean nullable() { + return false; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Forbidden.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Forbidden.java new file mode 100644 index 000000000..48c742f01 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Forbidden.java @@ -0,0 +1,27 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +public class Forbidden implements Validation { + + public ValidationResult validate() { + return this.validate(null); + } + + @Override + public ValidationResult validity(T s) { + return ValidationResult.invalid(ValidationError.builder() + .errorType("Forbidden") + .errorCode(NubeException.ErrorCode.INSUFFICIENT_PERMISSION_ERROR) + .message("you are not authorized to post this value")); + } + + @Override + public boolean nullable() { + return false; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/IntegerValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/IntegerValidation.java new file mode 100644 index 000000000..b786a356f --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/IntegerValidation.java @@ -0,0 +1,15 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.DataTypeValidation; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class IntegerValidation extends DataTypeValidation { + + @Override + protected Class classType() { + return Integer.class; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonArrayValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonArrayValidation.java new file mode 100644 index 000000000..9ad718f62 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonArrayValidation.java @@ -0,0 +1,16 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.DataTypeValidation; + +import io.vertx.core.json.JsonArray; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class JsonArrayValidation extends DataTypeValidation { + + @Override + protected Class classType() { + return JsonArray.class; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonObjectValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonObjectValidation.java new file mode 100644 index 000000000..e4e438e4a --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/JsonObjectValidation.java @@ -0,0 +1,16 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.DataTypeValidation; + +import io.vertx.core.json.JsonObject; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class JsonObjectValidation extends DataTypeValidation { + + @Override + protected Class classType() { + return JsonObject.class; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Max.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Max.java new file mode 100644 index 000000000..a72c75ba3 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Max.java @@ -0,0 +1,41 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Max implements Validation { + + protected final Double value; + + @Override + public ValidationResult validity(T s) { + if (s instanceof Number) { + Double d = Double.parseDouble(s.toString()); + if (condition(d)) { + return ValidationResult.valid(); + } else { + return ValidationResult.invalid(getErrorMessage(s)); + } + } else { + return ValidationResult.invalid(ValidationError.builder() + .errorType("ClassCastException") + .value(s.toString()) + .message("is not parsable to Number")); + } + } + + protected ValidationError.Builder getErrorMessage(T s) { + return ValidationError.builder() + .value(s.toString()) + .message("is not less than or equal to " + value.toString()); + } + + protected boolean condition(Double d) { + return value >= d; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Min.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Min.java new file mode 100644 index 000000000..a6a4c9914 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Min.java @@ -0,0 +1,21 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.exceptions.ValidationError; + +public class Min extends Max { + + public Min(Double value) { + super(value); + } + + protected ValidationError.Builder getErrorMessage(T s) { + return ValidationError.builder() + .value(s.toString()) + .message("is not greater than or equal to " + value.toString()); + } + + protected boolean condition(Double d) { + return value <= d; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberOrStringValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberOrStringValidation.java new file mode 100644 index 000000000..a698f6f85 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberOrStringValidation.java @@ -0,0 +1,15 @@ +package com.nubeiot.core.validator.validations; + +import java.util.Arrays; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +public class NumberOrStringValidation implements Validation { + + @Override + public ValidationResult validity(T s) { + return new Alternative<>(Arrays.asList(new NumberValidation<>(), new StringValidation<>())).validate(s); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberValidation.java new file mode 100644 index 000000000..e8a6d2ac3 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/NumberValidation.java @@ -0,0 +1,12 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.DataTypeValidation; + +public class NumberValidation extends DataTypeValidation { + + @Override + protected Class classType() { + return Number.class; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Range.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Range.java new file mode 100644 index 000000000..506483316 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Range.java @@ -0,0 +1,37 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.utils.Strings; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Range implements Validation { + + private final Double minValue; + private final Double maxValue; + + @Override + public ValidationResult validity(T s) { + if (s instanceof Number) { + Double d = Double.parseDouble(s.toString()); + if (minValue <= d && d < maxValue) { + return ValidationResult.valid(); + } else { + return ValidationResult.invalid(ValidationError.builder() + .value(s.toString()) + .message( + Strings.format("should be on range [{0}<= x < {1}]", + minValue, maxValue))); + } + } else { + return ValidationResult.invalid(ValidationError.builder() + .errorType("ClassCastException") + .value(s.toString()) + .message("is not parsable to Number")); + } + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/RegexValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/RegexValidation.java new file mode 100644 index 000000000..078988cda --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/RegexValidation.java @@ -0,0 +1,29 @@ +package com.nubeiot.core.validator.validations; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.nubeiot.core.exceptions.ValidationError; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class RegexValidation implements Validation { + + private final String regex; + + @Override + public ValidationResult validity(T s) { + Pattern pattern = java.util.regex.Pattern.compile(regex); + Matcher matcher = pattern.matcher(s.toString()); + boolean isValid = matcher.matches(); + + return isValid + ? ValidationResult.valid() + : ValidationResult.invalid( + ValidationError.builder().value(s.toString()).message("does not match with the pattern " + regex)); + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/Required.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Required.java new file mode 100644 index 000000000..17c6d87a0 --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/Required.java @@ -0,0 +1,24 @@ +package com.nubeiot.core.validator.validations; + +import java.util.Arrays; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Required implements Validation { + + private final Validation validation; + + @Override + public ValidationResult validity(T s) { + return new Composition<>(Arrays.asList(validation, new Exist<>())).validate(s); + } + + public boolean nullable() { + return false; + } + +} diff --git a/core/validator/src/main/java/com/nubeiot/core/validator/validations/StringValidation.java b/core/validator/src/main/java/com/nubeiot/core/validator/validations/StringValidation.java new file mode 100644 index 000000000..655f23c6b --- /dev/null +++ b/core/validator/src/main/java/com/nubeiot/core/validator/validations/StringValidation.java @@ -0,0 +1,15 @@ +package com.nubeiot.core.validator.validations; + +import com.nubeiot.core.validator.DataTypeValidation; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class StringValidation extends DataTypeValidation { + + @Override + protected Class classType() { + return String.class; + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationHolderTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationHolderTest.java new file mode 100644 index 000000000..08d74b850 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationHolderTest.java @@ -0,0 +1,78 @@ +package core.nubeiot.core.validator; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.JsonValidationHolder; +import com.nubeiot.core.validator.ValidationResult; +import com.nubeiot.core.validator.Validator; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Composition; +import com.nubeiot.core.validator.validations.IntegerValidation; +import com.nubeiot.core.validator.validations.RegexValidation; +import com.nubeiot.core.validator.validations.StringValidation; + +import io.vertx.core.json.JsonObject; + +public class JsonValidationHolderTest { + + @Test + public void test_jsonValidationHolderExecute() { + JsonObject jsonObject = new JsonObject().put("first_name", "Shane") + .put("last_name", 11) + .put("email", "shane@gmail.com") + .put("details", new JsonObject().put("age", 26.2)); + + String emailRegex = "\\b[\\w.%-]+@[-.\\w]+\\.[A-Za-z]{2,4}\\b"; + + JsonValidationHolder jsonValidationHolder = new JsonValidationHolder<>(); + jsonValidationHolder.add("first_name", new StringValidation<>()); + jsonValidationHolder.add("last_name", new StringValidation<>()); + jsonValidationHolder.add("email", new Composition<>( + Arrays.asList(new StringValidation<>(), new RegexValidation<>(emailRegex)))); + jsonValidationHolder.add("details.age", new IntegerValidation<>()); + + ValidationResult validationResult = Validator.builder() + .validations(Collections.singletonList(jsonValidationHolder)) + .build() + .execute(jsonObject); + String expected = + "ValidationError: last_name is not the type of java.lang.String && ValidationError: details.age is not " + + "the type of java.lang.Integer"; + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validationResult.getErrors()).getMessage(), + expected); + } + + @Test + public void test_jsonValidationHolderEagerExecute() { + JsonObject jsonObject = new JsonObject().put("first_name", "Shane") + .put("last_name", 11) + .put("email", "shane@gmail.com") + .put("details", new JsonObject().put("age", 26.2)); + + String emailRegex = "\\b[\\w.%-]+@[-.\\w]+\\.[A-Za-z]{2,4}\\b"; + + JsonValidationHolder jsonValidationHolder = new JsonValidationHolder<>(); + jsonValidationHolder.add("first_name", new StringValidation<>()); + jsonValidationHolder.add("last_name", new StringValidation<>()); + jsonValidationHolder.add("email", new Composition<>( + Arrays.asList(new StringValidation<>(), new RegexValidation<>(emailRegex)))); + jsonValidationHolder.add("details.age", new IntegerValidation<>()); + + ValidationResult validationResult = Validator.builder() + .validations(Collections.singletonList(jsonValidationHolder)) + .build() + .eagerExecute(jsonObject); + String expected = + "ValidationError: last_name is not the type of java.lang.String && ValidationError: details.age is not " + + "the type of java.lang.Integer"; + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validationResult.getErrors()).getMessage(), + expected); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationLoopingTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationLoopingTest.java new file mode 100644 index 000000000..131ce4dd8 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationLoopingTest.java @@ -0,0 +1,79 @@ +package core.nubeiot.core.validator; + +import java.net.URL; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.utils.FileUtils; +import com.nubeiot.core.validator.JsonValidationLoop; +import com.nubeiot.core.validator.ValidationResult; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.IntegerValidation; +import com.nubeiot.core.validator.validations.JsonArrayValidation; +import com.nubeiot.core.validator.validations.StringValidation; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class JsonValidationLoopingTest { + + @Test + public void test_jsonArrayLoop() { + URL resource = JsonValidationLoopingTest.class.getClassLoader().getResource("example.json"); + JsonArray jsonArray = new JsonArray(FileUtils.readFileToString(resource.toString())); + + JsonValidationLoop outerLoop = new JsonValidationLoop<>(""); + outerLoop.add("first_name", new StringValidation<>()); + outerLoop.add("last_name", new StringValidation<>()); + outerLoop.add("siblings", new JsonArrayValidation<>()); + + JsonValidationLoop interestsLoop = new JsonValidationLoop<>("details.basic.interests"); + interestsLoop.add("sports", new JsonArrayValidation<>()); + + JsonValidationLoop musicsLoop = new JsonValidationLoop<>("music"); + musicsLoop.add("classical", new JsonArrayValidation<>()); + musicsLoop.add("rock", new JsonArrayValidation<>()); + + interestsLoop.add(musicsLoop); // adding JsonValidationLoop inside JsonValidationLoop + + outerLoop.add(interestsLoop); // adding JsonValidationLoop inside JsonValidationLoop + + ValidationResult validationResult = outerLoop.validate(jsonArray); + System.out.println(validationResult.getErrors()); + NubeException nubeException = ValidationUtils.convertValidationErrorsToException.apply( + validationResult.getErrors()); + String expected = + "ValidationError: [1].details.basic.interests.[0].music.[1].rock is not the type of io.vertx.core.json" + + ".JsonArray && ValidationError: [1].details.basic.interests.[1].sports is not the type of io.vertx.core" + + ".json.JsonArray"; + Assert.assertEquals(expected, nubeException.getMessage()); + } + + @Test + public void test_jsonArrayAndJsonObject() { + URL resource = JsonValidationLoopingTest.class.getClassLoader().getResource("loop_and_key_loop_example.json"); + JsonObject jsonObject = new JsonObject(FileUtils.readFileToString(resource.toString())); + + JsonValidationLoop outerLoop = new JsonValidationLoop<>(""); + outerLoop.add("type", new StringValidation<>()); + outerLoop.add("value", new IntegerValidation<>()); + outerLoop.add("attributes", new JsonArrayValidation<>()); + + JsonValidationLoop attributesLoop = new JsonValidationLoop<>("attributes"); + attributesLoop.add("", new StringValidation<>()); + + outerLoop.add(attributesLoop); + + ValidationResult validationResult = outerLoop.validate(jsonObject); + System.out.println(validationResult.getErrors()); + NubeException nubeException = ValidationUtils.convertValidationErrorsToException.apply( + validationResult.getErrors()); + String expected = + "ValidationError: UI2.value is not the type of java.lang.Integer && ValidationError: UI2.attributes.[0]" + + " is not the type of java.lang.String"; + Assert.assertEquals(expected, nubeException.getMessage()); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationWhenTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationWhenTest.java new file mode 100644 index 000000000..15f0e6ff5 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/JsonValidationWhenTest.java @@ -0,0 +1,77 @@ +package core.nubeiot.core.validator; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.validator.JsonValidationWhen; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Any; +import com.nubeiot.core.validator.validations.Contains; +import com.nubeiot.core.validator.validations.Forbidden; + +import io.vertx.core.json.JsonObject; + +public class JsonValidationWhenTest { + + @Test + public void test_when_initialization_failure() { + JsonObject jsonObject = new JsonObject().put("id", 1).put("value", "ON"); + + JsonValidationWhen validation = new JsonValidationWhen<>().registerField("value"); + validation.registerIs("id", new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3)))) + .registerThen(new Contains<>(new HashSet<>(Arrays.asList("ON", "OFF")))); + + NubeException exception = ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(jsonObject).getErrors()); + + Assert.assertEquals(exception.getMessage(), + "ValidationError: JsonValidationWhen validation Initialization Error"); + Assert.assertEquals(exception.getErrorCode(), NubeException.ErrorCode.INITIALIZER_ERROR); + } + + @Test + public void test_when_success() { + /* Scenario 1: + 1. JsonValidationWhen id is in [1, 2, 3] then it's values should be valid on ["ON" or "OFF"] + 2. Otherwise, Forbidden*/ + JsonObject jsonObject = new JsonObject().put("id", 1).put("value", "ON"); + + JsonValidationWhen validation = new JsonValidationWhen<>().registerField("value"); + validation.registerIs("id", new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3)))) + .registerThen(new Contains<>(new HashSet<>(Arrays.asList("ON", "OFF")))) + .registerOtherwise(new Forbidden<>()); + + System.out.println( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(jsonObject).getErrors()) + .getMessage()); + Assert.assertTrue(validation.validate(jsonObject).isValid()); + } + + @Test + public void test_when_failure() { + /* Scenario 2: + 1. JsonValidationWhen id is in [1] then it's values should be valid on ["ON" or "OFF"] + 2. Otherwise, jsonValidationWhen id is in [2] then it's value should be valid on ["OFF"] + 3. Otherwise, pass any value as valid*/ + JsonObject jsonObject = new JsonObject().put("id", 2).put("value", "ON"); + + JsonValidationWhen when = new JsonValidationWhen<>().registerField("value"); + + when.registerIs("id", new Contains<>(new HashSet<>(Collections.singletonList(1)))) + .registerThen(new Contains<>(new HashSet<>(Arrays.asList("ON", "OFF")))) + .registerOtherwise( + new JsonValidationWhen<>().registerIs("id", new Contains<>(new HashSet<>(Collections.singletonList(2)))) + .registerThen(new Contains<>(new HashSet<>(Collections.singletonList("OFF")))) + .registerOtherwise(new Any<>())); + + NubeException nubeException = ValidationUtils.convertValidationErrorsToException.apply( + when.validate(jsonObject).getErrors()); + Assert.assertEquals(nubeException.getMessage(), "ValidationError: value strictly should fall in the [OFF]"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/RegisterDefaultValueTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/RegisterDefaultValueTest.java new file mode 100644 index 000000000..15020673b --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/RegisterDefaultValueTest.java @@ -0,0 +1,31 @@ +package core.nubeiot.core.validator; + +import java.util.Collections; + +import org.junit.Test; + +import com.nubeiot.core.utils.JsonUtils; +import com.nubeiot.core.validator.JsonValidationHolder; +import com.nubeiot.core.validator.Validator; +import com.nubeiot.core.validator.validations.Range; + +import io.vertx.core.json.JsonObject; + +public class RegisterDefaultValueTest { + + @Test + public void test_default_value_insertion_success() { + JsonObject jsonObject = new JsonObject(); + JsonObject defaultJsonObject = new JsonObject().put("a", 18); + + JsonValidationHolder jsonValidationHolder = new JsonValidationHolder<>(); + jsonValidationHolder.add("a", new Range<>(1d, 17d)); + Validator validation = Validator.builder() + .validations(Collections.singletonList(jsonValidationHolder)) + .mergeFunc(JsonUtils.mergeJsonObjectFunc) + .defaultValue(defaultJsonObject) + .build(); + System.out.println(validation.execute(jsonObject).getErrors()); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/utils/ValidationUtilsTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/utils/ValidationUtilsTest.java new file mode 100644 index 000000000..032960d4a --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/utils/ValidationUtilsTest.java @@ -0,0 +1,5 @@ +package core.nubeiot.core.validator.utils; + +public class ValidationUtilsTest { + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/AlternativeTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/AlternativeTest.java new file mode 100644 index 000000000..a2726de35 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/AlternativeTest.java @@ -0,0 +1,42 @@ +package core.nubeiot.core.validator.validations; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Alternative; +import com.nubeiot.core.validator.validations.IntegerValidation; +import com.nubeiot.core.validator.validations.NumberValidation; +import com.nubeiot.core.validator.validations.StringValidation; + +public class AlternativeTest { + + @Test + public void test_string_or_number_success() { + List> validations = new ArrayList<>(); + validations.add(new NumberValidation<>()); + validations.add(new StringValidation<>()); + + Validation validation = new Alternative<>(validations); + Assert.assertTrue(validation.validate("10").isValid()); + Assert.assertTrue(validation.validate(10d).isValid()); + } + + @Test + public void test_string_or_number_failure() { + List> validations = new ArrayList<>(); + validations.add(new NumberValidation<>()); + validations.add(new IntegerValidation<>()); + + Validation validation = new Alternative<>(validations); + System.out.println( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate("1").getErrors()) + .getMessage()); + Assert.assertFalse(validation.validate("1").isValid()); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/AnyTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/AnyTest.java new file mode 100644 index 000000000..00f108d35 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/AnyTest.java @@ -0,0 +1,18 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.validations.Any; + +public class AnyTest { + + @Test + public void test_any_success() { + + Assert.assertTrue(new Any<>().validate().isValid()); + Assert.assertTrue(new Any<>().validate(null).isValid()); + Assert.assertTrue(new Any<>().validate("XYZ").isValid()); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/CompositionTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/CompositionTest.java new file mode 100644 index 000000000..73a9577bf --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/CompositionTest.java @@ -0,0 +1,41 @@ +package core.nubeiot.core.validator.validations; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Composition; +import com.nubeiot.core.validator.validations.Max; +import com.nubeiot.core.validator.validations.Min; + +public class CompositionTest { + + @Test + public void test_number_with_min_max_success() { + List> validations = new ArrayList<>(); + validations.add(new Min<>(1d)); + validations.add(new Max<>(16d)); + + Validation validation = new Composition<>(validations); + + Assert.assertTrue(validation.validate(10).isValid()); + } + + @Test + public void test_number_with_min_max_failure() { + List> validations = new ArrayList<>(); + validations.add(new Min<>(1d)); + validations.add(new Max<>(16d)); + + Validation validation = new Composition<>(validations); + + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(17).getErrors()).getMessage(), + "ValidationError: 17 is not less than or equal to 16.0"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/ContainsTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ContainsTest.java new file mode 100644 index 000000000..18e3acdda --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ContainsTest.java @@ -0,0 +1,48 @@ +package core.nubeiot.core.validator.validations; + +import java.util.Arrays; +import java.util.HashSet; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Contains; + +import io.vertx.core.json.JsonArray; + +public class ContainsTest { + + @Test + public void test_contains_success() { + Validation validation = new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)), false); + + Assert.assertTrue(validation.validate(new JsonArray().add(1).add(2)).isValid()); + Assert.assertTrue(validation.validate(1).isValid()); + } + + @Test + public void test_contains_failure() { + Validation validation = new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5))); + Assert.assertEquals(ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(new JsonArray().add(1).add(8)).getErrors()).getMessage(), + "ValidationError: [1,8] strictly should fall in the [1, 2, 3, 4, 5]"); + } + + @Test + public void test_contains_when_strict_false_success() { + Validation validation = new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)), false); + Assert.assertTrue(validation.validate(new JsonArray().add(1).add(8)).isValid()); + } + + @Test + public void test_contains_when_strict_false_failure() { + Validation validation = new Contains<>(new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)), false); + + Assert.assertEquals(ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(new JsonArray().add(7).add(8)).getErrors()).getMessage(), + "ValidationError: [7,8] should fall in the [1, 2, 3, 4, 5]"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/ExistTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ExistTest.java new file mode 100644 index 000000000..a63ea1eac --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ExistTest.java @@ -0,0 +1,27 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Exist; + +public class ExistTest { + + @Test + public void test_exist_success() { + Validation validation = new Exist<>(); + Assert.assertTrue(validation.validate(1).isValid()); + } + + @Test + public void test_exist_failure() { + Validation validation = new Exist<>(); + Assert.assertFalse(validation.validate(null).isValid()); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(null).errors()).getMessage(), + "ValidationError: required value is null"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/ForbiddenTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ForbiddenTest.java new file mode 100644 index 000000000..8947843df --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/ForbiddenTest.java @@ -0,0 +1,23 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Forbidden; + +public class ForbiddenTest { + + @Test + public void test_forbidden_success() { + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(new Forbidden<>().validate().errors()) + .getMessage(), + "Forbidden: you are not authorized to post this value"); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(new Forbidden<>().validate("XYZ").errors()) + .getMessage(), + "Forbidden: you are not authorized to post this value"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/IntegerValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/IntegerValidationTest.java new file mode 100644 index 000000000..dae0371ed --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/IntegerValidationTest.java @@ -0,0 +1,84 @@ +package core.nubeiot.core.validator.validations; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.JsonValidationHolder; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.ValidationResult; +import com.nubeiot.core.validator.Validator; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.IntegerValidation; + +import io.vertx.core.json.JsonObject; + +public class IntegerValidationTest { + + @Test + public void test_integer_success() { + Validation validation = new IntegerValidation<>(); + Assert.assertTrue(validation.validate(1).errors().isEmpty()); + + Validator validator = Validator.builder().validations(Collections.singletonList(validation)).build(); + Assert.assertTrue(validator.execute(1).isValid()); + } + + @Test + public void test_integer_failure() { + JsonObject jsonObject = new JsonObject(); + JsonObject source = jsonObject; + if (source.getJsonObject("a") == null) { + source.put("a", new JsonObject()); + } + source = source.getJsonObject("a"); + + source.put("b", 5); + System.out.println(jsonObject); + + Validation validation = new IntegerValidation<>(); + Assert.assertEquals(validation.validate(1.1).errors().size(), 1); + Assert.assertEquals(validation.validate(1.1).errors().get(0).build().execute().getMessage(), + "ValidationError: 1.1 is not the type of java.lang.Integer"); + } + + @Test + public void test_integer_json_holder_validation() { + Validation validation = new IntegerValidation<>(); + JsonObject jsonObject = new JsonObject().put("a", 1.1).put("b", 1.2); + JsonValidationHolder jsonValidationHolder1 = new JsonValidationHolder<>(); + JsonValidationHolder jsonValidationHolder2 = new JsonValidationHolder<>(); + jsonValidationHolder1.add("a", validation); + jsonValidationHolder2.add("b", validation); + ValidationResult validationResult = Validator.builder() + .validations( + Arrays.asList(jsonValidationHolder1, jsonValidationHolder2)) + .build() + .execute(jsonObject); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validationResult.errors()).getMessage(), + "ValidationError: a is not the type of java.lang.Integer && ValidationError: b is not the" + + " type of java.lang.Integer"); + } + + @Test + public void test_integer_json_holder_eager_validation() { + Validation validation = new IntegerValidation<>(); + JsonObject jsonObject = new JsonObject().put("a", new JsonObject().put("x", "1.1")).put("b", 1.2); + JsonValidationHolder jsonValidationHolder1 = new JsonValidationHolder<>(); + JsonValidationHolder jsonValidationHolder2 = new JsonValidationHolder<>(); + jsonValidationHolder1.add("a.x", validation); + jsonValidationHolder2.add("b", validation); + ValidationResult eagerValidationResult = Validator.builder() + .validations(Arrays.asList(jsonValidationHolder1, + jsonValidationHolder2)) + .build() + .eagerExecute(jsonObject); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(eagerValidationResult.errors()).getMessage(), + "ValidationError: a.x is not the type of java.lang.Integer"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonArrayValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonArrayValidationTest.java new file mode 100644 index 000000000..afef60a21 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonArrayValidationTest.java @@ -0,0 +1,32 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.JsonArrayValidation; + +import io.vertx.core.json.JsonArray; + +public class JsonArrayValidationTest { + + @Test + public void test_json_array_success() { + Validation validation = new JsonArrayValidation<>(); + + Assert.assertTrue(validation.validate(new JsonArray().add(1).add(2).add(3)).isValid()); + } + + @Test + public void test_json_array_failure() { + Validation validation = new JsonArrayValidation<>(); + + NubeException nubeException = ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(1).getErrors()); + Assert.assertEquals(nubeException.getMessage(), + "ValidationError: 1 is not the type of io.vertx.core.json.JsonArray"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonObjectValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonObjectValidationTest.java new file mode 100644 index 000000000..d92e239c3 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/JsonObjectValidationTest.java @@ -0,0 +1,34 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.exceptions.NubeException; +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.JsonObjectValidation; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +public class JsonObjectValidationTest { + + @Test + public void test_json_object_success() { + Validation validation = new JsonObjectValidation<>(); + + Assert.assertTrue(validation.validate(new JsonObject().put("result", "success")).isValid()); + } + + @Test + public void test_json_object_failure() { + Validation validation = new JsonObjectValidation<>(); + + NubeException nubeException = ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(new JsonArray().add(1).add(2)).getErrors()); + + Assert.assertEquals(nubeException.getMessage(), + "ValidationError: [1,2] is not the type of io.vertx.core.json.JsonObject"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/MaxTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/MaxTest.java new file mode 100644 index 000000000..70fc08662 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/MaxTest.java @@ -0,0 +1,28 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Max; + +public class MaxTest { + + @Test + public void test_max_success() { + Validation validation = new Max<>(15d); + Assert.assertTrue(validation.validate(10.00).isValid()); + Assert.assertTrue(validation.validate(10).isValid()); + } + + @Test + public void test_max_failure() { + + Validation validation = new Max<>(15d); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(40d).errors()).getMessage(), + "ValidationError: 40.0 is not less than or equal to 15.0"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/MinTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/MinTest.java new file mode 100644 index 000000000..030b7a085 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/MinTest.java @@ -0,0 +1,27 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Min; + +public class MinTest { + + @Test + public void test_min_success() { + Validation validation = new Min<>(5d); + Assert.assertTrue(validation.validate(10.0).isValid()); + Assert.assertTrue(validation.validate(10).isValid()); + } + + @Test + public void test_min_failure() { + Validation validation = new Min<>(5d); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(4.0).errors()).getMessage(), + "ValidationError: 4.0 is not greater than or equal to 5.0"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberOrStringValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberOrStringValidationTest.java new file mode 100644 index 000000000..d9393fc7e --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberOrStringValidationTest.java @@ -0,0 +1,31 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.NumberOrStringValidation; + +import io.vertx.core.json.JsonObject; + +public class NumberOrStringValidationTest { + + @Test + public void test_numeric_or_string_success() { + Validation validation = new NumberOrStringValidation<>(); + Assert.assertTrue(validation.validate(1).isValid()); + Assert.assertTrue(validation.validate(1.0).isValid()); + Assert.assertTrue(validation.validate("ABC").isValid()); + } + + @Test + public void test_numeric_or_string_failure() { + Validation validation = new NumberOrStringValidation<>(); + + System.out.println(ValidationUtils.convertValidationErrorsToException.apply( + validation.validate(new JsonObject().put("a", 1)).getErrors()).getMessage()); + Assert.assertFalse(validation.validate(new JsonObject().put("a", 1)).isValid()); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberValidationTest.java new file mode 100644 index 000000000..b522285a6 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/NumberValidationTest.java @@ -0,0 +1,27 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.NumberValidation; + +public class NumberValidationTest { + + @Test + public void test_numeric_success() { + Validation validation = new NumberValidation<>(); + Assert.assertTrue(validation.validate(1).isValid()); + Assert.assertTrue(validation.validate(1.0).isValid()); + } + + @Test + public void test_numeric_failure() { + Validation validation = new NumberValidation<>(); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate("1").errors()).getMessage(), + "ValidationError: 1 is not the type of java.lang.Number"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/RangeTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RangeTest.java new file mode 100644 index 000000000..d483aa99c --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RangeTest.java @@ -0,0 +1,30 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.Range; + +public class RangeTest { + + @Test + public void test_range_success() { + Validation validation = new Range<>(5d, 15d); + Assert.assertTrue(validation.validate(10.0).isValid()); + Assert.assertTrue(validation.validate(10).isValid()); + } + + @Test + public void test_range_failure() { + Validation validation = new Range<>(5d, 15d); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(4.0).errors()).getMessage(), + "ValidationError: 4.0 should be on range [5.0<= x < 15.0]"); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate("4d").errors()).getMessage(), + "ClassCastException: 4d is not parsable to Number"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/RegexValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RegexValidationTest.java new file mode 100644 index 000000000..574b4b556 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RegexValidationTest.java @@ -0,0 +1,28 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.RegexValidation; + +public class RegexValidationTest { + + @Test + public void test_regex_success() { + + Validation validation = new RegexValidation("[0-9]+"); + Assert.assertTrue(validation.validate("0123456").isValid()); + } + + @Test + public void test_regex_failure() { + Validation validation = new RegexValidation("[0-9]+"); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate("012ABC").errors()) + .getMessage(), + "ValidationError: 012ABC does not match with the pattern [0-9]+"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/RequiredTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RequiredTest.java new file mode 100644 index 000000000..a949af618 --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/RequiredTest.java @@ -0,0 +1,31 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.IntegerValidation; +import com.nubeiot.core.validator.validations.Required; + +public class RequiredTest { + + @Test + public void test_required_integer_success() { + Validation validation = new Required<>(new IntegerValidation<>()); + Assert.assertTrue(validation.validate(1).isValid()); + } + + @Test + public void test_required_integer_failure() { + Validation validation = new Required<>(new IntegerValidation<>()); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate("1").getErrors()).getMessage(), + "ValidationError: 1 is not the type of java.lang.Integer"); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(null).getErrors()) + .getMessage(), + "ValidationError: null required value is null"); + } + +} diff --git a/core/validator/src/test/java/core/nubeiot/core/validator/validations/StringValidationTest.java b/core/validator/src/test/java/core/nubeiot/core/validator/validations/StringValidationTest.java new file mode 100644 index 000000000..77c97458a --- /dev/null +++ b/core/validator/src/test/java/core/nubeiot/core/validator/validations/StringValidationTest.java @@ -0,0 +1,27 @@ +package core.nubeiot.core.validator.validations; + +import org.junit.Assert; +import org.junit.Test; + +import com.nubeiot.core.validator.Validation; +import com.nubeiot.core.validator.utils.ValidationUtils; +import com.nubeiot.core.validator.validations.StringValidation; + +public class StringValidationTest { + + @Test + public void test_string_success() { + String message = "Hello World!"; + Validation validation = new StringValidation<>(); + Assert.assertTrue(validation.validate(message).isValid()); + } + + @Test + public void test_string_failure() { + Validation validation = new StringValidation<>(); + Assert.assertEquals( + ValidationUtils.convertValidationErrorsToException.apply(validation.validate(1).errors()).getMessage(), + "ValidationError: 1 is not the type of java.lang.String"); + } + +} diff --git a/core/validator/src/test/resources/example.json b/core/validator/src/test/resources/example.json new file mode 100644 index 000000000..b155cd8db --- /dev/null +++ b/core/validator/src/test/resources/example.json @@ -0,0 +1,40 @@ +[ + { + "first_name": "Binod", + "last_name": "Rai", + "siblings": [ + "Guna", + "Rina" + ] + }, + { + "first_name": "Binod", + "last_name": "Rai", + "details": { + "basic": { + "interests": [ + { + "sports": [ + "Cricket", + "Tennis" + ], + "music": [ + { + "classical": [ + "Baroque", + "Choral" + ] + }, + { + "rock": "Arena Rock" + } + ] + }, + { + "sports": "Football" + } + ] + } + } + } +] diff --git a/core/validator/src/test/resources/loop_and_key_loop_example.json b/core/validator/src/test/resources/loop_and_key_loop_example.json new file mode 100644 index 000000000..c66cc9977 --- /dev/null +++ b/core/validator/src/test/resources/loop_and_key_loop_example.json @@ -0,0 +1,14 @@ +{ + "UI1": { + "type": "abc", + "value": 12 + }, + "UI2": { + "type": "xyz", + "value": "True", + "attributes": [ + 123, + "ABC" + ] + } +} diff --git a/settings.gradle b/settings.gradle index 2ea2ad61f..428f50ba4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ include ':core:httpserver' include ':core:auth' include ':core:kafka' include ':core:common' +include ':core:validator' include ':dashboard:server' include ':dashboard:connector:edge'