Skip to content

About

Simple Functional Validation Lib - OMG, yet another validation lib

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

Contributors Forks Stargazers Issues Apache 2.0 License LinkedIn

SimFun Validation

Table of Contents
  1. About The Project - or simply "WHY???"
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgements

OMG, yet another validation framework - WHY???

Ok, ok. I must admit, there are a gazillion of validation frameworks out there. However, none of them really fits my needs. And here, is why:

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public Mono<UserDto> storeUser(@RequestBody UserDto userDto) {
        return Validation
            .of(userDto)
            .with(Objects::nonNull)
            .with(user -> Objects.nonNull(user.username()))
            .with(user -> user.age() >= ADULT_LEGAL_AGE, user -> "The user is too young.")
            .validate()
            .map(userGateway::storeUser)
            .orElseGet((result, errors) -> Mono.error(() -> new UserValidationException(errors)));
    }
}

As you can see, the validation definition is clear and concise. The functional approach lets you build up pipelines which are executed depending on the validation results.

Comparison simfun-validation to VAVR

Let's compare this lib to VAVR, which also contains a validation piece:

VAVR

public class Vavr {

    public void validate() {
        PersonValidator personValidator = new PersonValidator();

        // Valid(Person(John Doe, 30))
        Validation<Seq<String>, Person> valid = personValidator.validatePerson("John Doe", 30);

        // Invalid(List(Name contains invalid characters: '!4?', Age must be greater than 0))
        Validation<Seq<String>, Person> invalid = personValidator.validatePerson("John? Doe!4", -1);
        //A valid value is contained in a Validation.Valid instance, a list of validation errors is contained in a Validation.Invalid instance.
    }
    
    //The following validator is used to combine different validation results to one Validation instance.
    class PersonValidator {

        private static final String VALID_NAME_CHARS = "[a-zA-Z ]";
        private static final int MIN_AGE = 0;

        public Validation<Seq<String>, Person> validatePerson(String name, int age) {
            return Validation.combine(validateName(name), validateAge(age)).ap(Person::new);
        }

        private Validation<String, String> validateName(String name) {
            return CharSeq.of(name).replaceAll(VALID_NAME_CHARS, "").transform(seq -> seq.isEmpty()
                ? Validation.valid(name)
                : Validation.invalid("Name contains invalid characters: '"
                + seq.distinct().sorted() + "'"));
        }

        private Validation<String, Integer> validateAge(int age) {
            return age < MIN_AGE
                ? Validation.invalid("Age must be at least " + MIN_AGE)
                : Validation.valid(age);
        }
    }
}

simfun-validation

public class SimFunValidation {

    public void validate() {

        var valid = new Person("John Doe", 30);
        var invalid = new Person("John? Doe!4", -1);

        var validValues = List.of(valid, invalid)
            .stream()
            .map(person -> Validation
                .of(person)
                .with(checkedPerson -> checkedPerson.getName().matches("[a-zA-Z ]+"), invalidPerson -> "Name contains invalid characters")
                .with(checkedPerson -> MIN_AGE < checkedPerson.getAge(), invalidPerson -> "Age must be at least " + MIN_AGE)
                .validate())
            .filter(ValidationResult::isSuccessful)
            .map(ValidationResult::get)
            .collect(Collectors.toList());
    }
}

Built With

The project uses plain java without any other dependency pulled in. However, it uses Java 11 features (var) and thus can only be used in projects that use Java 11 or above by themselves.

Getting Started

Prerequisites

This lib uses Java 11 features. Thus, your project needs to be using at least Java 11. A backport is possible (replace var all over the place) - but hey, why?

Installation

Just declare a new dependency in your dependency management system as such:

Maven

<dependency>
    <groupId>org.simfun</groupId>
    <artifactId>validation</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle

dependencies { testImplementation "org.simfun:validation:1.0.0" }

Usage

The functional approach

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public Mono<UserDto> storeUser(@RequestBody UserDto userDto) {
        return Validation
            .of(userDto)
            .with(Objects::nonNull)
            .with(user -> Objects.nonNull(user.username()))
            .with(user -> user.age() >= ADULT_LEGAL_AGE, user -> "The user is too young.")
            .validate()
            .map(userGateway::storeUser)
            .orElseGet((result, errors) -> Mono.error(() -> new UserValidationException(errors)));
    }
}

The more traditional-Java approach

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public UserDto storeUser(@RequestBody UserDto userDto) {
        var validationResult = Validation
            .of(userDto)
            .with(Objects::nonNull)
            .with(user -> Objects.nonNull(user.username()))
            .with(user -> user.age() >= ADULT_LEGAL_AGE)
            .validate();

        if (validationResult.isSuccessful()) {
            return userGateway.storeUser(userDto);
        }

        throw new UserValidationException(validationResult.getMessages());
    }
}

The validation lib takes in any value, even null, as a value to be checked. However, it will reject null as value for a check and will also reject null for an error message provider.

Allowed

    var validation=Validation.of(null);

Throws a NullPointerException

    var badCheckUsage=Validation
    .of("")
    .with(null);  //NPE thrown here due to check being null

    var badMessageProviderUsage=Validation
    .of("")
    .with(valueToCheck->true,null);  //NPE thrown here due to error message provider being null

Combining Validations

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public Mono<UserDto> storeUser(@RequestBody UserDto userDto) {
        return Validation
            .of(user)
            .with(Objects::nonNull)
            .validate()
            .flatMap(user -> Validation
                .of(user.firstName)
                .with(name -> Character.isLowerCase(name.charAt(0)), name -> String.format("[%s] doesn't start with a lower case letter", name))
                .with(name -> Character.isUpperCase(name.charAt(name.length() - 1)), name -> String.format("[%s] doesn't end with an upper case letter", name))
                .validate()
                .map(firstName -> userDto))
            .map(userGateway::storeUser)
            .orElseGet((result, errors) -> Mono.error(() -> new UserValidationException(errors)));
    }
}

Roadmap

See the open issues for a list of proposed features (and known issues).

Contributing

Contributions are what make the open source community such an amazing place to be, learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Please find more information in CONTRIBUTING.md

License

Distributed under the MIT License. See LICENSE.md for more information.

Contact

Your Name - @lars_opitz

Project Link: https://github.com/lopitz/simfun-validation

About

Simple Functional Validation Lib - OMG, yet another validation lib

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages