Code Monkey home page Code Monkey logo

yavi's Introduction

YAVI (Yet Another ValIdation)

Apache 2.0 Maven Central Javadocs Actions Status

YAVI Logo

YAVI (pronounced jɑ-vάɪ) is a lambda based type safe validation for Java.

Why YAVI?

YAVI sounds as same as a Japanese slang "YABAI (ヤバイ)" that means awesome or awful depending on the context (like "Crazy"). If you use YAVI, you will surely understand that it means the former.

The concepts are

  • No reflection!
  • No (runtime) annotation!
  • Not only Java Beans!
  • Zero dependency!

If you are not a fan of Bean Validation, YAVI will be an awesome alternative.

YAVI has the following features:

  • Type-safe constraints, unsupported constraints cannot be applied to the wrong type
  • Fluent and intuitive API
  • Constraints on any object. Java Beans, Records, Protocol Buffers, Immutables and anything else.
  • Lots of powerful built-in constraints
  • Easy custom constraints
  • Validation for groups, conditional validation
  • Validation for arguments before creating an object
  • Support for API and combination of validation results and validators that incorporate the concept of functional programming

See the reference documentation for details.

Presentations

Getting Started

This content is derived from https://hibernate.org/validator/documentation/getting-started/

Welcome to YAVI.

The following paragraphs will guide you through the initial steps required to integrate YAVI into your application.

Prerequisites

Project set up

In order to use YAVI within a Maven project, simply add the following dependency to your pom.xml:

<dependency>
    <groupId>am.ik.yavi</groupId>
    <artifactId>yavi</artifactId>
    <version>0.14.1</version>
</dependency>

This tutorial uses JUnit 5 and AssertJ. Add the following dependencies as needed:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
</dependency>

Applying constraints

Let’s dive into an example to see how to apply constraints:

Create src/main/java/com/example/Car.java and write the following code.

package com.example;

import am.ik.yavi.builder.ValidatorBuilder;
import am.ik.yavi.core.Validator;

public record Car(String manufacturer, String licensePlate, Integer seatCount) {
    public static final Validator<Car> validator = ValidatorBuilder.<Car>of()
            .constraint(Car::manufacturer, "manufacturer", c -> c.notNull())
            .constraint(Car::licensePlate, "licensePlate", c -> c.notNull().greaterThanOrEqual(2).lessThanOrEqual(14))
            .constraint(Car::seatCount, "seatCount", c -> c.greaterThanOrEqual(2))
            .build();
}

The ValidatorBuilder.constraint is used to declare the constraints which should be applied to the return values of getter for the Car instance:

  • manufacturer must never be null
  • licensePlate must never be null and must be between 2 and 14 characters long
  • seatCount must be at least 2

You can find the complete source code on GitHub.

Validating constraints

To perform a validation of these constraints, you use a Validator instance. To demonstrate this, let’s have a look at a simple unit test:

Create src/test/java/com/example/CarTest.java and write the following code.

package com.example;

import am.ik.yavi.core.ConstraintViolations;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class CarTest {

    @Test
    void manufacturerIsNull() {
        final Car car = new Car(null, "DD-AB-123", 4);
        final ConstraintViolations violations = Car.validator.validate(car);

        assertThat(violations.isValid()).isFalse();
        assertThat(violations).hasSize(1);
        assertThat(violations.get(0).message()).isEqualTo("\"manufacturer\" must not be null");
    }

    @Test
    void licensePlateTooShort() {
        final Car car = new Car("Morris", "D", 4);
        final ConstraintViolations violations = Car.validator.validate(car);

        assertThat(violations.isValid()).isFalse();
        assertThat(violations).hasSize(1);
        assertThat(violations.get(0).message()).isEqualTo("The size of \"licensePlate\" must be greater than or equal to 2. The given size is 1");
    }

    @Test
    void seatCountTooLow() {
        final Car car = new Car("Morris", "DD-AB-123", 1);
        final ConstraintViolations violations = Car.validator.validate(car);

        assertThat(violations.isValid()).isFalse();
        assertThat(violations).hasSize(1);
        assertThat(violations.get(0).message()).isEqualTo("\"seatCount\" must be greater than or equal to 2");
    }

    @Test
    void carIsValid() {
        final Car car = new Car("Morris", "DD-AB-123", 2);
        final ConstraintViolations violations = Car.validator.validate(car);

        assertThat(violations.isValid()).isTrue();
        assertThat(violations).hasSize(0);
    }
}

Validator instances are thread-safe and may be reused multiple times.

The validate() method returns a ConstraintViolations instance, which you can iterate in order to see which validation errors occurred. The first three test methods show some expected constraint violations:

  • The notNull() constraint on manufacturer is violated in manufacturerIsNull()
  • The greaterThanOrEqual(int) constraint on licensePlate is violated in licensePlateTooShort()
  • The greaterThanOrEqual(int) constraint on seatCount is violated in seatCountTooLow()

If the object validates successfully, validate() returns an empty ConstraintViolations as you can see in carIsValid(). You can also check if the validation was successful with the ConstraintViolations.isValid method.

Where to go next?

That concludes the 5 minutes tour through the world of YAVI. If you want a more complete introduction, it is recommended to read "Using YAVI" in the reference document.

Required

  • Java 8+

License

Licensed under the Apache License, Version 2.0.

yavi's People

Contributors

arielberkovich avatar be-hase avatar codacy-badger avatar ddzida avatar dependabot-preview[bot] avatar dependabot[bot] avatar diegokrupitza avatar duponter avatar ffroliva avatar gakuzzzz avatar hartmut-co-uk avatar lonre avatar making avatar sdobrovolschi avatar zhanpon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yavi's Issues

Custom constraint with dependencies

Hello,
thank you so much for your great work, i need to validate a dto based on the presence or absence of an object contained within mongodb. In this case I need to inject a repository. How can I do this with yavi?

Support unique lists

It would be nice to validate that a list must contain unique elements, and isn't allowed to contain any duplicates.

feat(kotlin): access to the kotlin extension method for

Hi,

FYI, I've switch my project (at work) from BeanValidation to YAVI and it's a pleasure to use it. By doing this, we will also simplify the switch to purely functional / no-reflection application 🎉

In this issue I would like to know if we can add the extension method for the constraintOnObject to avoid using it like this in kotlin env:

internal data class Foo(
        val name: String?,
        val uid: String?,
        val activated: Boolean = false,
        val startDate: OffsetDateTime = OffsetDateTime.now(),
        val endDate: OffsetDateTime?
): Validatable {

    fun configurationAsJson() = configuration?.toString() ?: "{}"

    override fun validate() = validator.validate(this)

    companion object {
        val validator = Validator.builder<SubscriptionSave>()
                .constraint(SubscriptionSave::name) { notBlank().message("{0} must not be null or empty") }
                .constraintOnObject(Foo::endDate, "endDate") { it.notNull().message("{0} must not be null") }
                .constraintOnObject(Foo::contract, "contract") { it.notNull().message("{0} must not be null") }
                .constraintOnObject(Foo::service, "service") { it.notNull().message("{0} must not be null") }
                .constraintOnTarget({ it.startDate.isBefore(it.endDate ?: OffsetDateTime.now().plusDays(1))}, "date.validity", of("startDate.endDate", "The start date must be before the end date"))
                .build()
    }
}

I would like to replace lines

.constraintOnObject(Foo::service, "service") { it.notNull().message("{0} must not be null") }

by something like this

.constraintOnObject(Foo::service) { notNull().message("{0} must not be null") }

I've tried to fork the project to do a PR but I think this modification is more complexe than I thought.

PS: If' you have any comment on my code example, feel free to share it 👍

Thanks for your help and this wonderful library !

The collection validator is ignored in a nested validator

Given the following class hierarchy

class Root {
   Parent parent;
}

class Parent {
   List<Child> children;
}

class Child {
   String name;
}

The validation of children is ignored.

var childValidator = ValidatorBuilder.of(Chidl.class)
                    .constraint(Child::getName, "name", Constraint::notNull)
                    .build();

var parentValidator = ValidatorBuilder.of(Parent.class)
                    .forEach(Parent::getChildrent, "children", childValidator)
                    .build();

var validator = ValidatorBuilder.of(Root.class)
                    .nest(Root::getParent, "parent", parentValidator)
                    .build()

The issue is that nested collection validators are not appended to the main validator. Please see https://github.com/making/yavi/blob/develop/src/main/java/am/ik/yavi/builder/ValidatorBuilder.java#L694

Change `constraint` method name for Kotlin to `konstraint`

Since ValidatorBuilder has a bunch of constraint overloaded methods, it's hard for Kotlin users to find a proper method in the list (it will be shown at the tail of the list)
image

Changing the name to konstraint for Kotlin would make it much easier

image

Add Annotation Processor to generate constraints metadata and make YAVI more type safe

public class Car {
	private final String name;
	private final int gas;

	public Car(String name, int gas) {
		this.name = name;
		this.gas = gas;
	}

	@ConstraintTarget
	public String getName() {
		return name;
	}

	@ConstraintTarget
	public int getGas() {
		return gas;
	}
}

will generate

public class _CarMeta {
	public static final am.ik.yavi.meta.StringConstraintMeta<test.Car> NAME = new am.ik.yavi.meta.StringConstraintMeta<test.Car>() {

		@Override
		public String name() {
			return "name";
		}

		@Override
		public java.util.function.Function<test.Car, java.lang.String> toValue() {
			return test.Car::getName;
		}
	};

	public static final am.ik.yavi.meta.IntegerConstraintMeta<test.Car> GAS = new am.ik.yavi.meta.IntegerConstraintMeta<test.Car>() {

		@Override
		public String name() {
			return "gas";
		}

		@Override
		public java.util.function.Function<test.Car, java.lang.Integer> toValue() {
			return test.Car::getGas;
		}
	};
}

This metadata can be used in ValidatorBuilder

Validator<Car> validator = ValidatorBuilder.<Car> of()
	.constraint(_CarMeta.NAME, c -> c.notEmpty())
	.constraint(_CarMeta.GAS, c -> c.greaterThan(0))
	.build();

ArgumentsValidator should also be supported.

public class Person {

	private final String firstName;

	private final String lastName;

	private final int age;

	@ConstraintArguments
	public Person(String firstName, String lastName, int age) {
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}
}

will generate

public class _PersonArgumentsMeta {

	public static final am.ik.yavi.meta.StringConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>> FIRSTNAME = new am.ik.yavi.meta.StringConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>>() {

		@Override
		public String name() {
			return "firstName";
		}

		@Override
		public java.util.function.Function<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>, java.lang.String> toValue() {
			return am.ik.yavi.arguments.Arguments1::arg1;
		}
	};

	public static final am.ik.yavi.meta.StringConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>> LASTNAME = new am.ik.yavi.meta.StringConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>>() {

		@Override
		public String name() {
			return "lastName";
		}

		@Override
		public java.util.function.Function<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>, java.lang.String> toValue() {
			return am.ik.yavi.arguments.Arguments2::arg2;
		}
	};

	public static final am.ik.yavi.meta.IntegerConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>> AGE = new am.ik.yavi.meta.IntegerConstraintMeta<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>>() {

		@Override
		public String name() {
			return "age";
		}

		@Override
		public java.util.function.Function<am.ik.yavi.arguments.Arguments3<java.lang.String, java.lang.String, java.lang.Integer>, java.lang.Integer> toValue() {
			return am.ik.yavi.arguments.Arguments3::arg3;
		}
	};
}

This metadata can be used in ArgumentsValidatorBuilder

Arguments3Validator<String, String, Integer, Person> validator = ArgumentsValidatorBuilder
	.of(Person::new)
	.builder(b -> b
		.constraint(_PersonArgumentsMeta.FIRSTNAME,  -> c.greaterThanOrEqual(1).lessThanOrEqual(50))
		.constraint(_PersonArgumentsMeta.LASTNAME, c -> c.greaterThanOrEqual(1).lessThanOrEqual(50))
		.constraint(_PersonArgumentsMeta.AGE, c -> c.greaterThanOrEqual(20).lessThanOrEqual(99)))
	.build();

Person person = validator.validated("John", "Doe", 20);

Doubt about validation with multiples parameters.

Is it possible create something like:

fun validateZipCode(zipCode: String, country: String): Boolean {
  return when (country) {
    "Portugal" -> onlyNumbers(zipCode).length == 7
    "USA" -> onlyNumbers(zipCode).length == 5
    // other countries....
    else -> false
  }
}

and then:

val addressValidator: Validator<Address> = ValidatorBuilder.of(Address::class.java) {
    use my validateZipCode()
}

Is the Validator thread safe ?

Hello,

We've been using this class as a singleton for a while now and never had any problem. But the Validator Java doc seems to imply that its use "must be thread safe". So i'm a bit confused as even in the official documentation the webflux example does not seem to be very thread safe.

What do you think about this ?

Add Kotlin suspending function variant to fold

I think it would be useful to add a suspend fun <L, R, U> Either<L, R>.fold(leftMapper: suspend (L) -> U, rightMapper: suspend (R) -> U) Kotlin extension in order to be able to use suspending functions here.

Support Bean Validation Annotations via Annotation Processor to generate a Validator

public class Message {
	@NotEmpty
	private String text;

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}
}

import am.ik.yavi.builder.ValidatorBuilder;
import am.ik.yavi.core.Validator;

public class Message_Validator {
	public static final Validator<Message> INSTANCE = ValidatorBuilder.<Message>of()
			.constraint(Message::getText, "text", c -> c.notEmpty())
			.build();
}

Nested validator do not use passed ConstraintGroup if declared inside constraintOnCondition

Hi. I found similar issue to #24. It happens when you define nested validator inside constraintOnCondition. It passes DEFAULT instead of chosen ConstraintGroup.

public class GroupTest {

    private Validator<MainObject> mainObjectValidator;

    class MainObject {
        Long id;
        NestedObject nested;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public NestedObject getNested() {
            return nested;
        }

        public void setNested(NestedObject nested) {
            this.nested = nested;
        }
    }

    class NestedObject {
        Long id;
        String text;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }
    }

    @BeforeEach
    void setUp() {

        Validator<NestedObject> nestedObjectValidator = ValidatorBuilder.<NestedObject>of()
                .constraintOnCondition((nestedObject, constraintGroup) -> {
                    System.out.println(constraintGroup.name());//prints DEFAULT
                    return constraintGroup.equals(ValidationGroups.CREATE);
                }, b -> b.constraint(NestedObject::getId, "id", Constraint::isNull))
                .constraintOnCondition((nestedObject, constraintGroup) -> {
                    System.out.println(constraintGroup.name());
                    return constraintGroup.equals(ValidationGroups.UPDATE);
                }, b -> b.constraint(NestedObject::getId, "id", Constraint::notNull))
                .constraint(NestedObject::getText, "text", CharSequenceConstraint::notBlank)
                .build();

        mainObjectValidator = ValidatorBuilder.<MainObject>of()
                .constraintOnCondition(ValidationGroups.CREATE.toCondition(), b -> b.constraint(MainObject::getId, "id", Constraint::isNull))
                .constraintOnCondition(ValidationGroups.UPDATE.toCondition(), b -> b.constraint(MainObject::getId, "id", Constraint::notNull))
                .constraintOnCondition(ValidationGroups.CREATE.toCondition(), b -> b.nest(MainObject::getNested, "nested", nestedObjectValidator))
                .constraintOnCondition(ValidationGroups.UPDATE.toCondition(), b -> b.nest(MainObject::getNested, "nested", nestedObjectValidator))
                .build();
    }

    @Test
    void shouldBeValid() {
        var target = new MainObject();
        target.setId(1L);

        var nested = new NestedObject();
        nested.setId(1L);
        nested.setText("test");
        target.setNested(nested);

        var result = mainObjectValidator.validate(target, ValidationGroups.UPDATE);

        assertTrue(result.isValid());
    }

    @Test
    void shouldBeInvalid() {
        var target = new MainObject();
        target.setId(1L);

        var nested = new NestedObject();
        nested.setId(1L);
        target.setNested(nested);

        var result = mainObjectValidator.validate(target, ValidationGroups.CREATE);

        assertFalse(result.isValid());
        /*below assert fails because after validation there is only 2 errors (id in MainObject and lack
        of text in NestedObject)*/
        assertEquals(3, result.size());
    }


}

nestIfPresent with conditional constraint throws exception when target is null

Hi @making

I'm not sure it is a bug but when I use nestIfPresent with conditional constraint and target is null then I get an exception (NullPointerException).

For example, I have the following test:

 public class OptionalNestedConstraintWithConditionConstraintTest {
    @Test
    void shouldBeValidAddressWithoutCountry() {
        Validator<Address> addressValidator = ValidatorBuilder.of(Address.class)
                .constraint(Address::getCity, "city", city -> city.notBlank())
                .nestIfPresent(Address::getCountry, "country", countryValidatorBuilder -> countryValidatorBuilder
                        .constraint(Country::getCode, "code", code -> code.lessThan(3))
                        .constraintOnCondition((country, group) -> country.getName() != null, conditionalCountryBuilder -> conditionalCountryBuilder
                                .constraint(Country::getCode, "code", code -> code.notBlank())))
                .build();
        Address address = new Address("Paris", null);

        ConstraintViolations violations = addressValidator.validate(address);

        assertThat(violations.isValid()).isTrue();
    }

    @Test
    void shouldBeValidPersonWithoutAddress() {
        Validator<Person> personValidator = ValidatorBuilder.of(Person.class)
                .constraint(Person::getName, "name", name -> name.notBlank())
                .nestIfPresent(Person::getAddress, "address", addressValidatorBuilder -> addressValidatorBuilder
                        .constraint(Address::getCity, "city", city -> city.notBlank())
                        .nestIfPresent(Address::getCountry, "country", countryValidatorBuilder -> countryValidatorBuilder
                                .constraint(Country::getCode, "code", code -> code.lessThan(3))
                                .constraintOnCondition((country, group) -> country.getName() != null, conditionalCountryBuilder -> conditionalCountryBuilder
                                        .constraint(Country::getCode, "code", code -> code.notBlank()))
                        ))
                .build();
        Person person = new Person("Jack", null);

        ConstraintViolations violations = personValidator.validate(person);

        assertThat(violations.isValid()).isTrue();
    }

    private static class Person {
        private String name;
        private Address address;

        public Person(String name, Address address) {
            this.name = name;
            this.address = address;
        }

        public String getName() {
            return name;
        }

        public Address getAddress() {
            return address;
        }
    }

    private static class Address {
        private String city;
        private Country country;

        public Address(String city, Country country) {
            this.city = city;
            this.country = country;
        }

        public String getCity() {
            return city;
        }

        public Country getCountry() {
            return country;
        }
    }

    private static class Country {
        private String code;
        private String name;

        public Country(String code, String name) {
            this.code = code;
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public String getName() {
            return name;
        }
    }
}

I get the following exceptions:
a)

java.lang.NullPointerException
	at am.ik.yavi.core.OptionalNestedConstraintWithConditionConstraintTest.lambda$null$2(OptionalNestedConstraintWithConditionConstraintTest.java:15)
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:34)
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:20)
	at am.ik.yavi.core.Validator.lambda$validate$3(Validator.java:328)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at am.ik.yavi.core.Validator.validate(Validator.java:326)
	at am.ik.yavi.core.Validator.validate(Validator.java:152)
	at am.ik.yavi.core.Validator.validate(Validator.java:165)
	at am.ik.yavi.core.OptionalNestedConstraintWithConditionConstraintTest.shouldBeValidAddressWithoutCountry(OptionalNestedConstraintWithConditionConstraintTest.java:20)

b)

java.lang.NullPointerException
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:33)
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:20)
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:34)
	at am.ik.yavi.core.NestedConstraintCondition.test(NestedConstraintCondition.java:20)
	at am.ik.yavi.core.Validator.lambda$validate$3(Validator.java:328)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at am.ik.yavi.core.Validator.validate(Validator.java:326)
	at am.ik.yavi.core.Validator.validate(Validator.java:152)
	at am.ik.yavi.core.Validator.validate(Validator.java:165)
	at am.ik.yavi.core.OptionalNestedConstraintWithConditionConstraintTest.shouldBeValidPersonWithoutAddress(OptionalNestedConstraintWithConditionConstraintTest.java:39)

In this case I expect that conditional constraints are not evaluated so I do not have to check nullable in the predicate. Maybe I'm wrong. I would be appreciate for clarification.

I checked this in 0.6.1 and 0.7.0 and the results are the same.

custom validation

I have a validation that takes a string and returns a boolean. As I enter in the other validations.
Example:

val validator: Validator<User> = ValidatorBuilder.of<User>()
 .konstraint(User::name).myCustomValidation().message("some message")

Nested validator do not use passed ConstraintGroup

Hi. When I invoke validate with ConstraintGroup it seems that it is not passed to nested validator. Nested object validation works but it do not support mentioned feature.

public class GroupTest {
    
    private Validator<MainObject> mainObjectValidator;

    class MainObject {
        Long id;
        NestedObject nested;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public NestedObject getNested() {
            return nested;
        }

        public void setNested(NestedObject nested) {
            this.nested = nested;
        }
    }

    class NestedObject {
        Long id;
        String text;

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }
    }

    @BeforeEach
    void setUp() {

        Validator<NestedObject> nestedObjectValidator = ValidatorBuilder.<NestedObject>of()
                .constraintOnCondition(ValidationGroups.CREATE.toCondition(), b -> b.constraint(NestedObject::getId, "id", Constraint::isNull))
                .constraintOnCondition(ValidationGroups.UPDATE.toCondition(), b -> b.constraint(NestedObject::getId, "id", Constraint::notNull))
                .constraint(NestedObject::getText, "text", CharSequenceConstraint::notBlank)
                .build();

        mainObjectValidator = ValidatorBuilder.<MainObject>of()
                .constraintOnCondition(ValidationGroups.CREATE.toCondition(), b -> b.constraint(MainObject::getId, "id", Constraint::isNull))
                .constraintOnCondition(ValidationGroups.UPDATE.toCondition(), b -> b.constraint(MainObject::getId, "id", Constraint::notNull))
                .nest(MainObject::getNested, "nested", nestedObjectValidator)
                .build();
    }

    @Test
    void shouldBeValid() {
        var target = new MainObject();
        target.setId(1L);

        var nested = new NestedObject();
        nested.setId(1L);
        nested.setText("test");
        target.setNested(nested);

        var result = mainObjectValidator.validate(target, ValidationGroups.UPDATE);

        assertTrue(result.isValid());
    }

    @Test
    void shouldBeInvalid() {
        var target = new MainObject();
        target.setId(1L);

        var nested = new NestedObject();
        nested.setId(1L);
        target.setNested(nested);

        var result = mainObjectValidator.validate(target, ValidationGroups.CREATE);

        assertFalse(result.isValid());
        /*below assert fails because after validation there is only 2 errors (id in MainObject and lack    
        of text in NestedObject)*/
        assertEquals(3, result.size());
    }

}

Deep nesting: Bug or misunderstanding?

So... I guess i found something wrong with nested nestIfPresent
I'm using version 0.4.0

Should the code below throw an exception or not?
In this test, there is a violation in the second validation

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        Validator<Bar> barValidator = ValidatorBuilder.<Bar>of()
                .constraint(Bar::getValue, "name", Constraint::notNull)
                .build();
        Validator<Foo> fooValidator = ValidatorBuilder.<Foo>of()
                .nestIfPresent(Foo::getBar, "bar", barValidator)
                .build();
        Validator<Root> rootValidator = ValidatorBuilder.<Root>of()
                .nest(Root::getFoo, "foo", fooValidator)
                .build();

        // data
        Foo foo = new Foo(null);
        Root root = new Root(foo);

        // Working as expected
        ConstraintViolations fooViolations = fooValidator.validate(foo);
        if (!fooViolations.isEmpty()) throw new RuntimeException("no exceptions expected here");

        // Unexpected violation
        ConstraintViolations rootViolations = rootValidator.validate(root);
        if (!rootViolations.isEmpty()) throw new RuntimeException("neither here");
    }

    private class Root {
        private Foo foo;

        private Root(Foo foo) {
            this.foo = foo;
        }

        public Foo getFoo() {
            return foo;
        }
    }

    private class Foo {
        private Bar bar;

        private Foo(Bar bar) {
            this.bar = bar;
        }

        public Bar getBar() {
            return bar;
        }
    }

    private class Bar {
        private String value;

        private Bar(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }
}

And something similar is happening with forEach as well, I just couldn't find exacly what. Yet.

Could it be a bug or did I miss something?

Rename constraintForNested to nested

I'm thinking renaming constraintForNested since it is too long.

  • constraintForNested => nest
  • constraintIfPresentForNested => nestIfPresent

Java

before:

Validator<Address> validator = Validator.<Address> builder() //
            .constraintForNested(Address::getCountry, "country", //
                        b -> b.constraint(Country::getName, "name", c -> c.notBlank() //
                                                                        .lessThanOrEqual(20))) //
            .constraintForNested(Address::getCity, "city", //
                        b -> b.constraint(City::getName, "name", c -> c.notBlank() //
                                                                        .lessThanOrEqual(100))) //
            .build();

after:

Validator<Address> validator = Validator.<Address> builder() //
            .nest(Address::getCountry, "country", //
                        b -> b.constraint(Country::getName, "name", c -> c.notBlank() //
                                                                        .lessThanOrEqual(20))) //
            .nest(Address::getCity, "city", //
                        b -> b.constraint(City::getName, "name", c -> c.notBlank() //
                                                                        .lessThanOrEqual(100))) //
            .build();

Kotlin

        val validator = Validator.builder<PostSnippet>()
            .constraintForNested(PostSnippet::snippet) {
                constraint(Text::text) {
                    notEmpty().lessThanOrEqual(3)
                }
            }
            .build()

after:

        val validator = Validator.builder<PostSnippet>()
            .nest(PostSnippet::snippet) {
                constraint(Text::text) {
                    notEmpty().lessThanOrEqual(3)
                }
            }
            .build()

validate a class, that the attribute is another class

I have this:

data class Address(
    override val id: UUID?,
    override val active: Boolean,
    val country: String,
    val state: String,
    val city: String,
    val zipCode: String,
    val district: String,
    val publicPlace: String,
    val number: String,
    val complement: String,
    val serviceReference: String,
    val serviceFk: UUID?
) : BaseModel()

and the validation

val addressValidator: Validator<Address> = ValidatorBuilder.of(Address::class.java)
    .konstraint(Address::country) {
        notNull().message("Required the country name.")
        lessThan(121).message("The country name must contain less than 120 characters.")
        greaterThan(2).message("The country name must contain more than 3 characters.")
    }
    .konstraint(Address::state) {
        notNull().message("Required the state name.")
        lessThan(121).message("The state name must contain less than or equal to 120 characters.")
        greaterThan(1).message("The state name must contain more than 1 characters.")
    }
    .konstraint(Address::city) {
        notNull().message("Required the city name.")
        lessThan(121).message("The city name must contain less than or equal to 120 characters.")
        greaterThan(2).message("The city name must contain more than 2 characters.")
    }
    .konstraint(Address::district) {
        notNull().message("Required the district name.")
        lessThan(121).message("The district name must contain less than or equal to 120 characters.")
        greaterThan(2).message("The district name must contain more than 2 characters.")
    }
    .konstraint(Address::publicPlace) {
        notNull().message("Required the public place name.")
        lessThan(121).message("The public place name must contain less than or equal to 120 characters.")
        greaterThan(2).message("The public place name must contain more than 2 characters.")
    }
    .konstraint(Address::number) {
        notNull().message("Required the number name.")
        lessThan(121).message("The number name must contain less than or equal to 120 characters.")
        greaterThan(2).message("The number name must contain more than 2 characters.")
    }
    .konstraint(Address::serviceReference) {
        notNull().message("Required the service reference name.")
        lessThan(121).message("The service reference name must contain less than or equal to 120 characters.")
        greaterThan(5).message("The district name must contain more than 5 characters.")
    }
    .build()

fun addressValidations(T: Address): Boolean {
    return addressValidator.validate(T).isValid
}

everything works ok.
But i have this:

data class OrchestrationPerson(
    val person: String,
    val address: Address
)

And I would like to validate the address: Address using the validation I already have.
Something like:

val orchestrationPersonValidator: Validator<OrchestrationPerson> = ValidatorBuilder.of<OrchestrationPerson>()
    .konstraint(OrchestrationPerson::address) {
        addressValidator
    }.build()

fun orchestrationPersonValidations(T: OrchestrationPerson): Boolean {
    return orchestrationPersonValidator.validate(T).isValid
}

But I'm getting an error. What is the correct way to do it?

formatting the error message

I'm trying to format the error output as follows:

{
   field : fieldName
   message: List of Message
}

for exemple:

{
    field : Name
    messagem : {
           "The Name field is required"
           "The Name field must contain between 4 and 100 characters"
    }

    // others fields / messages
}

What I've done so far:


fun errorMsg(T: ConstraintViolations): List<String> {
    val errorList = mutableListOf<String>()
    T.forEach { errors -> errorList.add(errors.message()) }
    return errorList

But I looked in the documentation and found nothing on how to capture the field that is in error.
There's something like:
ConstraintViolations.forEach {error -> error.field ()}

Question about usage performance

builder.constraintOnTarget(param -> {
  Document doc = documentDao.findById(param.getId());
  if (isNull(doc)) {
    return false;
  }
  return true;
}, "id", "not_exists", "id not exists");

builder.constraintOnTarget(param -> {
  Document doc = documentDao.findById(param.getId());
  if (!param.getOwnerId().equals(doc.getOwnerId())) {
    return false;
  }
  return true;
}, "ownerId", "no_permission", "no permission");

this validator will execute documentDao.findById twice, is it possible to make the documentDao.findById execute only once?

Thanks

Replace konstraint extensions by a validator Kotlin DSL

In order to provide a more idiomatic Kotlin experience, I am wondering if we could replace konstraint extensions introduced by #16 (I don't really like this kind of k prefixed to be honest) by a Kotlin DSL that would isolate more the Kotlin API from the Java one. I need to give it more thoughts but my initial proposal is the following:

val validator = validator<User> {
     constraint(User::name) {
          notNull()
          lessThanOrEqual(20)
     }
     constraint(User::email) {
          notNull()
          greaterThanOrEqual(5)
          lessThanOrEqual(50) //
          email()
     }
     constraint(User::age) {
          notNull()
          greaterThanOrEqual(0)
          lessThanOrEqual(200)
     }
}

Any thoughts? If you like it I can probably try to craft a PR.

Fail fast mode support?

allows to return from the current validation as soon as the first constraint violation occurs

Fix bottleneck on CharaSequenceConstraint

image

  • am.ik.yavi.constraint.CharSequenceConstraint#size
    • am.ik.yavi.constraint.CharSequenceConstraint#normalize
      • am.ik.yavi.constraint.charsequence.variant.VariantOptions#ignored

am.ik.yavi.constraint.charsequence.variant.VariantOptions#ignored is pretty slow.

Kotlin custom error message ignores arguments() override

Hi
Im trying to get yavi to display a custom error message but I dont seem to be able to get custom arguments to work.
Its either broken for kotlin or Im doing something wrong.
This is my code

@Service
class TagTagValidator : PassModelValidation<TagTag> {
    override fun buildValidator(validatorBuilder: ValidatorBuilder<TagTag>) =
        validatorBuilder.apply {
            konstraint(TagTag::name) {
                notNull()
                    .notBlank()
                    .predicate(ValidTagConstraint())
            }
        }
}

class ValidTagConstraint : CustomConstraint<String?> {

    private val actualCount = 0

    private val maxCount = 50

    override fun arguments(): Array<Any> {
        return arrayOf(this.maxCount, this.actualCount)
    }

    override fun messageKey(): String {
        return ""
    }

    override fun defaultMessageFormat(): String {
        return """The tag "{3}" may not be longer than {1} bytes. length is {2}"""
    }

    override fun test(tag: String?): Boolean {
        return (tag?.toByteArray()?.count() ?: 0) <= maxCount
    }

}

If I set a breakpoint into the override class it wont stop there

How do I implement List validation in Kotlin?

How do I implement List validation in Kotlin?

data class Form(
  val users: List<String>
) {
  companion object {
    val validator = ValidatorBuilder.of(Form::class.java)
       .forEach(GroupCreationRequest::members) {
           ValidatorBuilder.of(String::class.java)
               .konstraint(String::toString) { // <- ★ KProperty Only
                   notEmpty()
               }
               .build()
       }
       .build()
  }
}

feat(): validation involving multiple fields

Hi,

I really like the approach YAVI take to simplify the bean validation, but I would like to know how (and if it is possible simply) we can do a "two field validation"

For example:

data class Foo(val min: Int, val max: Int) // very simple model, can happen on more than two fields with more than one type

I would like to be valid only if min is smaller than max. I don't find anything about it in the current API which is very annoying for me because a huge part of my business domain has this kind of (form) validation on our class.

I find a solution by transforming the value to Map then doing the validation on each value... but it's not very efficient (putting into a map and then extracting it)

Thanks for your help

Support extending validator

It would be nice to be able to extend an existing validator to apply it on the root of the current validated object, in order to use a validator created for a super type (or just to create a hierarchy of validator)

For example, let's say I have two classes like these :

public class Person {
    private String name;

    public static final validator = ValidatorBuilder.of(Person.class)
                            .constraint(Person::getName, "name", Constraint::NotNull)
                            .build();

    // Etc...
}


public class Employee extends Person {
    private String serviceId;

    public static final validator = ValidatorBuilder.of(Employee.class)
                            // Apply the validator Person.validator here
                            .constraint(Employee::getServiceId, "service", Constraint::NotNull)
                            .build();

    // Etc ...
}

In this case, the work around I have come out with is .nest(Person.class::cast, "person", Person.validator) but it's not super readable. and I end up with a prefix "person.Xxx" which doesn't really mean anything.
Something like .extends(Person.validator) would be perfect

Provide an easy way to override violation default messages per constraint

Currently there is no way to override a violation message on a constraint.
The only way to override is implementing MessageFormatteer.

It would be convenient if a user can define a customized message on a constraint like following:

Validator<User> validator = Validator.<User> builder() //
	.constraint(User::getName, "name", c -> c.notNull().message("name is required!") //
				.greaterThanOrEqual(1).message("name is too large!") //
				.lessThanOrEqual(20).message("name is too small!")) //
	.build()

It requires some internal logic changes.

[proposal] Introduce `ArgumentNValidator` combinators

The current Validator<T> requires an instance of type T to use the validate method.
This must allow for the existence of T instance in an invalid state.

This Email example is a good example.
It provides a factory method to prevent creation with invalid strings, but the constructor is exposed to allow users to instantiate it with invalid values.

However, modern general practice recommends that classes should not be instantiated with invalid values.

If the constructor of Email is designed to throw a runtime exception when an invalid value is passed, it is not possible to define a Validator<Email>.

So, how about making it possible to separate the type of the value received by the Validator from the type of the result?

For example, the following.

public interface EssentialValidator<A, R> {

  Validated<R> validate(A argument);

  default <R2> EssentialValidator<A, R2> map(Function<? super R, ? extends R2> f) {
      return a -> EssentialValidator.this.validate(a).map(f);
  }

  default <A2> EssentialValidator<A2, R> contramap(Function<? super A2, ? extends A> f) {
      return a2 -> EssentialValidator.this.validate(f.apply(a2));
  }

}
public final class ApplicativeValidator<T> implements EssentialValidator<T, T> {
   ...

With such an interface, it is possible to define EssentialValidator<String, Email> as follows, even if Email is designed to throw an exception in its constructor.

EssentialValidator<String, Email> validator = ValidatorBuilder.<String>of()
        .constraint(s -> s, "email", c -> c.notBlank().lessThanOrEqual(128).email())
        .build()
        .applicative()
        .map(Email::new);

This approach is to define validators of a small type, and then combine them to create a large-structure validator.
However, YAVI is originally designed to create a large-structure validator by extracting parts of a large-structure object and writing constraint after constraint.
Therefore, I am not sure if this proposal is really necessary as a use case of YAVI or not.

Support Validation using Applicative Functor

Hey there, that's a great library! But I see an issue, similar to the same issue that affects validation via annotations. It is not that simple to define a reusable Value Object class, that is itself composed by other Value Object classes.

Example in Kotlin:

data class Email private constructor(val value: String){
   companion object {
       fun of(value: String): Either<ConstraintViolations, Email> = ValidatorBuilder.of<Email>()
            .konstraint(Email::value) {
                email()
            }.build()
            .validateToEither(Email(emailValue))
   }
}
data class Phone private constructor(val value: String){
   ... similar factory method here
}
data class ContactsInfo(val email: Email, val phone: PhoneNumber){
   companion object {
       fun of(emailValue: String, phoneValue: String): Either<ConstraintViolations, ContactsInfo>{
          val email: Either<ConstraintViolations, Email> = Email.of(emailValue)
          val phone: Either<ConstraintViolations, PhoneNumber> = PhoneNumber.of(phoneValue)
          
          ... How to compose email and phone here to build a ContactsInfo()? ...
       }
   }
}

Build a ConcactsInfo like in the example is not possible as Either lacks of a flatMap method. But also if flatMap was there, it would short-circuit on email, and then we would miss an eventual error on phone, in the list of ConstraintViolations.

I see only two ways out, in the current version of Yavi:

  1. making public the constructors of Email and PhoneNumber and having the of factory method return the ConstraintValidations, by calling validate instead of validateToEither. Then, every time check the value of the isValid property of ConstraintValidations. This solution is not explicit, neither type-safe, and drives to duplication.
  2. Use exceptions.

My solution proposal is: let's discuss a way of integrating Yavi with the Validation Monad of Konad in order to have an extension method like

fun <T> Validator<T>.validateToKonad(target: T): Validation<ConstraintViolations, T> =
    validate(target).run {
        if(isValid) target.success()
        else fail()
    }

With the Validation from Konad the example above would become

data class ContactsInfo(val email: Email, val phone: PhoneNumber){
   companion object {
       fun of(emailValue: String, phoneValue: String): Validation<ConstraintViolations, ContactsInfo> =
           ::ContactsInfo + Email.of(emailValue) + PhoneNumber.of(phoneValue)
   }
}

Support conditional constraint

@enricosecondulfo Thanks for the feedback in spring-projects-experimental/spring-fu#83 (comment)

Personally, I prefrer to define different validators for create and update or have different data class for create and update for explicitly. I had a bad experience to be cofused by abuse of grouping constraints with Bean Validation. That's why I scoped it out at first time.

But I could support conditional constraint with more functional style.

The idea in my mind is as following

ConstraintGroup group = ConstraintGroup.of("update");

Validator<User> validator = Validator.builder(User.class) //
		.constraintOnCondition(group.toCondition(), //
				b -> b.constraint(User::getName, "name",
						c -> c.lessThanOrEqual(10)))
		.build();

validator.validate(user, group);

Flexible conditons can be written by lamda expressions as well

ConstraintGroup group = ConstraintGroup.of("update");

Validator<User> validator = Validator.builder(User.class) //
		.constraintOnCondition((user, cg) -> {
			String name = cg.name();
			return name.equals("update") || name.equals("delete");
		}, b -> b.constraint(User::getName, "name", c -> c.lessThanOrEqual(10)))
		.build();

validator.validate(user, group);

constraintOnCondition() in nested validator not worked

Hi, thanks for very convenient library.
I found strange behavior.

When we use ValidatorBuilder#nest() and ValidatorBuilder#constraintOnCondition() in nested validator, validation result is not as I expected.

Reproduction test code is below.

class NestedAndCondition {

  ConstraintCondition<IntRange> whenAllOfNumbersNotNull =
      (r, g) -> r.small != null && r.big != null;

  Validator<IntRange> thenCompareNumbers =
      ValidatorBuilder.of(IntRange.class)
          .constraintOnTarget(
              r -> r.big > r.small, 
              "big", 
              null, 
              "[big] must be greater than [small]")
          .build();

  Validator<IntRange> intRangeValidator =
      ValidatorBuilder.of(IntRange.class)
          .constraint(
            (ValidatorBuilder.ToInteger<IntRange>) r -> r.small, 
            "small", 
            Constraint::notNull)
          .constraint(
            (ValidatorBuilder.ToInteger<IntRange>) r -> r.big, 
            "big", 
            Constraint::notNull)
          .constraintOnCondition(whenAllOfNumbersNotNull, thenCompareNumbers)
          .build();

  @Test
  void testStandAlone() {
    IntRange intRange = new IntRange(1, 0);

    assertThat(intRangeValidator.validate(intRange).isValid()).isFalse();
  }

  @Test
  void testNested() {

    Validator<Nested> validator =
        ValidatorBuilder.of(Nested.class)
            .nest(n -> n.intRange, "intRange", intRangeValidator)
            .build();

    Nested nested = new Nested(new IntRange(1, 0));

    assertThat(validator.validate(nested).isValid()).isFalse();
  }

  public static class Nested {
    public IntRange intRange;

    public Nested(IntRange intRange) {
      this.intRange = intRange;
    }
  }

  public static class IntRange {
    public Integer small;
    public Integer big;

    public IntRange(Integer small, Integer big) {
      this.small = small;
      this.big = big;
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.