Skip to content

Commit

Permalink
Merge pull request #4 from avaje/feature/validation-request
Browse files Browse the repository at this point in the history
Add ConstraintViolationException and ValidationRequest
  • Loading branch information
rob-bygrave authored Apr 20, 2023
2 parents 3787839 + a3f8ba1 commit 23f848f
Show file tree
Hide file tree
Showing 27 changed files with 631 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,29 @@
/**
* Describes a constraint violation. This object exposes the constraint violation context as well as
* the message describing the violation.
*
* @param <T> the type of the root bean
* @author Emmanuel Bernard
*/
public class ConstraintViolation {

private final String path;
private final String propertyName;
private final String message;

public ConstraintViolation(String message) {
public ConstraintViolation(String path, String propertyName, String message) {
this.path = path;
this.propertyName = propertyName;
this.message = message;
}

/** @return the interpolated error message for this constraint violation */
/** Return the path that this violation occurred for */
public String path() {
return path;
}

public String propertyName() {
return propertyName;
}

/** Return the interpolated error message for this constraint violation */
public String message() {
return message;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.avaje.validation;

import java.util.Set;

public final class ConstraintViolationException extends RuntimeException {

private final Set<ConstraintViolation> violations;

public ConstraintViolationException(Set<ConstraintViolation> violations) {
this.violations = violations;
}

public Set<ConstraintViolation> violations() {
return violations;
}
}
35 changes: 0 additions & 35 deletions validator/src/main/java/io/avaje/validation/ValidationAdapter.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package io.avaje.validation;

import java.util.Set;


public interface ValidationType<T> {

Set<ConstraintViolation> validate(T object);

void validate(T object, Set<ConstraintViolation> violations);
void validate(T object) throws ConstraintViolationException;
}
19 changes: 11 additions & 8 deletions validator/src/main/java/io/avaje/validation/Validator.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package io.avaje.validation;

import io.avaje.validation.adapter.AnnotationValidationAdapter;
import io.avaje.validation.adapter.CoreValidation;
import io.avaje.validation.adapter.ValidationAdapter;
import io.avaje.validation.adapter.ValidatorComponent;
import io.avaje.validation.core.DefaultBootstrap;
import io.avaje.validation.spi.Bootstrap;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;

import io.avaje.validation.core.DefaultBootstrap;
import io.avaje.validation.spi.Bootstrap;

public interface Validator {

void validate(Object any) throws ConstraintViolationException;

static Builder builder() {
final Iterator<Bootstrap> bootstrapService = ServiceLoader.load(Bootstrap.class).iterator();
if (bootstrapService.hasNext()) {
Expand All @@ -20,16 +24,15 @@ static Builder builder() {
return DefaultBootstrap.builder();
}

Set<ConstraintViolation> validate(Object any);

Set<ConstraintViolation> validate(Collection<Object> any);

<T> ValidationAdapter<T> adapter(Class<T> cls);

<T> ValidationAdapter<T> adapter(Type type);

<T> AnnotationValidationAdapter<T> annotationAdapter(Class<? extends Annotation> cls);

CoreValidation core();

/** Build the Validator instance adding ValidationAdapter, Factory or AdapterBuilder. */
interface Builder {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
package io.avaje.validation;
package io.avaje.validation.adapter;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import io.avaje.validation.Validator;
import io.avaje.validation.core.MessageInterpolator;

public interface AnnotationValidationAdapter<T> {
public interface AnnotationValidationAdapter<T> extends ValidationAdapter<T> {

void validate(T type, Set<ConstraintViolation> violations);
//void validate(T type, ValidationRequest req);

default AnnotationValidationAdapter<T> init(Map<String, String> annotationValueMap) {
return this;
}

default AnnotationValidationAdapter<T> andThen(AnnotationValidationAdapter<? super T> after) {
Objects.requireNonNull(after);
return (t, v) -> {
validate(t, v);
after.validate(t, v);
};
}
/** Factory for creating a ValidationAdapter. */
public interface Factory {

Expand All @@ -31,6 +24,6 @@ public interface Factory {
* <p>Returning null means that the adapter could be created by another factory.
*/
AnnotationValidationAdapter<?> create(
Type annotationType, Validator context, MessageInterpolator interpolator);
Type annotationType, Validator context, MessageInterpolator interpolator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.avaje.validation.adapter;

import java.util.Collection;

public interface CoreValidation {

/** Return true to continue validation on this value if needed */
boolean required(Object value, ValidationRequest ctx, String propertyName);

boolean collection(ValidationRequest ctx, String propertyName, Collection<?> collection, int minSize, int maxSize);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.avaje.validation.adapter;

public interface ScalarValidator<T> {

/** Return true to continue validation on this value if needed */
boolean optional(ValidationRequest ctx, T value);

/** Return true to continue validation on this value if needed */
boolean required(ValidationRequest ctx, T value);

/** Return true to continue validation on this value if needed */
boolean optional(ValidationRequest ctx, T value, int minLength, int maxLength);

/** Return true to continue validation on this value if needed */
boolean required(ValidationRequest ctx, T value, int minLength, int maxLength);

boolean min(ValidationRequest ctx, T value, T minValue);
boolean max(ValidationRequest ctx, T value, T maxValue);
boolean range(ValidationRequest ctx, T value, T minValue, T maxValue);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2014 Square, Inc.
*
* 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
*
* https://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 io.avaje.validation.adapter;

import io.avaje.validation.Validator;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Objects;

public interface ValidationAdapter<T> {

/** Return true if validation should recurse */
boolean validate(T value, ValidationRequest req, String propertyName);

default boolean validate(T value, ValidationRequest req) {
return validate(value, req, null);
}

default boolean validateAll(Collection<T> value, ValidationRequest req, String propertName) {
if (propertName != null) {
req.pushPath(propertName);
}
int index = -1;
for (T element : value) {
validate(element, req, String.valueOf(++index));
}
if (propertName != null) {
req.popPath();
}
return true;
}

default AnnotationValidationAdapter<T> andThen(ValidationAdapter<? super T> after) {
Objects.requireNonNull(after);
return (value, req, propertyName) -> {
if (validate(value, req, propertyName)) {
return after.validate(value, req, propertyName);
}
return true;
};
}

/** Factory for creating a ValidationAdapter. */
public interface Factory {

/**
* Create and return a ValidationAdapter given the type and annotations or return null.
*
* <p>Returning null means that the adapter could be created by another factory.
*/
ValidationAdapter<?> create(Type type, Validator jsonb);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.avaje.validation.adapter;

import io.avaje.validation.ConstraintViolation;
import io.avaje.validation.ConstraintViolationException;

import java.util.*;

public class ValidationRequest {

private final ArrayDeque<String> pathStack = new ArrayDeque<>();

private final Set<ConstraintViolation> violations = new LinkedHashSet<>();


public void addViolation(String msg, String propertyName) {
violations.add(new ConstraintViolation(currentPath(), propertyName, msg));
}

private String currentPath() {
StringJoiner joiner = new StringJoiner(".");
final var descendingIterator = pathStack.descendingIterator();
while (descendingIterator.hasNext()) {
joiner.add(descendingIterator.next());
}
return joiner.toString();
}


public void pushPath(String path) {
pathStack.push(path);
}

public void popPath() {
pathStack.pop();
}

public void throwWithViolations() {
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.avaje.validation;
package io.avaje.validation.adapter;

import io.avaje.validation.Validator;

/**
* User defined components to register custom JsonAdapters with Validator.Builder.
* <p>
* These are service loaded when Validator starts. They can be specified in
* {@code META-INF/services/io.avaje.validation.ValidatorComponent} or when using
* {@code META-INF/services/io.avaje.validation.adapter.ValidatorComponent} or when using
* java module system via a {@code provides} clause in module-info.
*/
@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.avaje.validation.AnnotationValidationAdapter;
import io.avaje.validation.ValidationAdapter;
import io.avaje.validation.adapter.AnnotationValidationAdapter;
import io.avaje.validation.adapter.ValidationAdapter;

/** Builds and caches the ValidationAdapter adapters for DValidator. */
final class CoreAdapterBuilder {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.avaje.validation.core;

import io.avaje.validation.adapter.CoreValidation;
import io.avaje.validation.adapter.ValidationRequest;

import java.util.Collection;

final class DCoreValidation implements CoreValidation {

@Override
public boolean required(Object value, ValidationRequest ctx, String propertyName) {
if (value == null) {
ctx.addViolation("Required", propertyName);
return false;
}
return true;
}

@Override
public boolean collection(ValidationRequest ctx, String propertyName, Collection<?> collection, int minSize, int maxSize) {
if (collection == null) {
if (minSize != -1) {
ctx.addViolation("CollectionNull", propertyName);
}
return false;
}
final int size = collection.size();
if (size < minSize) {
ctx.addViolation("CollectionMinSize", propertyName);
}
if (maxSize > 0 && size > maxSize) {
ctx.addViolation("CollectionMaxSize", propertyName);
}
// also validate the collection elements
return size > 0;
}
}
Loading

0 comments on commit 23f848f

Please sign in to comment.