/* __ __ __ __ __ ___ * \ \ / / \ \ / / __/ * \ \/ / /\ \ \/ / / * \____/__/ \__\____/__/.ɪᴏ * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ */ package io.vavr.control; import io.vavr.AbstractValueTest; import io.vavr.Value; import io.vavr.collection.CharSeq; import io.vavr.collection.Seq; import io.vavr.collection.List; import org.junit.Test; import java.util.ArrayList; import java.util.Objects; import java.util.Spliterator; public class ValidationTest extends AbstractValueTest { public static final String OK = "ok"; public static final List<String> ERRORS = List.of("error1", "error2", "error3"); // -- AbstractValueTest @Override protected <T> Validation<String, T> empty() { return Validation.invalid("empty"); } @Override protected <T> Validation<String, T> of(T element) { return Validation.valid(element); } @SafeVarargs @Override protected final <T> Value<T> of(T... elements) { return Validation.valid(elements[0]); } @Override protected boolean useIsEqualToInsteadOfIsSameAs() { return true; } @Override protected int getPeekNonNilPerformingAnAction() { return 1; } // -- Validation.valid @Test public void shouldCreateSuccessWhenCallingValidationSuccess() { assertThat(Validation.valid(1) instanceof Validation.Valid).isTrue(); } // -- Validation.invalid @Test public void shouldCreateFailureWhenCallingValidationFailure() { assertThat(Validation.invalid("error") instanceof Validation.Invalid).isTrue(); } // -- Validation.fromEither @Test public void shouldCreateFromRightEither() { Validation<String, Integer> validation = Validation.fromEither(Either.right(42)); assertThat(validation.isValid()).isTrue(); assertThat(validation.get()).isEqualTo(42); } @Test public void shouldCreateFromLeftEither() { Validation<String, Integer> validation = Validation.fromEither(Either.left("vavr")); assertThat(validation.isValid()).isFalse(); assertThat(validation.getError()).isEqualTo("vavr"); } // -- Validation.narrow @Test public void shouldNarrowValid() { Validation<String, Integer> validation = Validation.valid(42); Validation<CharSequence, Number> narrow = Validation.narrow(validation); assertThat(narrow.get()).isEqualTo(42); } @Test public void shouldNarrowInvalid() { Validation<String, Integer> validation = Validation.invalid("vavr"); Validation<CharSequence, Number> narrow = Validation.narrow(validation); assertThat(narrow.getError()).isEqualTo("vavr"); } // -- Validation.sequence @Test(expected = NullPointerException.class) public void shouldThrowWhenSequencingNull() { Validation.sequence(null); } @Test public void shouldCreateValidWhenSequencingValids() { final Validation<Seq<String>, Seq<Integer>> actual = Validation.sequence(List.of( Validation.valid(1), Validation.valid(2) )); assertThat(actual).isEqualTo(Validation.valid(List.of(1, 2))); } @Test public void shouldCreateInvalidWhenSequencingAnInvalid() { final Validation<Seq<String>, Seq<Integer>> actual = Validation.sequence(List.of( Validation.valid(1), Validation.invalid(List.of("error1", "error2")), Validation.valid(2), Validation.invalid(List.of("error3", "error4")) )); assertThat(actual).isEqualTo(Validation.invalid(List.of("error1", "error2", "error3", "error4"))); } // -- toEither @Test public void shouldConvertToRightEither() { Either<?, Integer> either = Validation.valid(42).toEither(); assertThat(either.isRight()).isTrue(); assertThat(either.get()).isEqualTo(42); } @Test public void shouldConvertToLeftEither() { Either<String, ?> either = Validation.invalid("vavr").toEither(); assertThat(either.isLeft()).isTrue(); assertThat(either.getLeft()).isEqualTo("vavr"); } // -- filter @Test public void shouldFilterValid() { Validation<String, Integer> valid = Validation.valid(42); assertThat(valid.filter(i -> true).get()).isSameAs(valid); assertThat(valid.filter(i -> false)).isSameAs(Option.none()); } @Test public void shouldFilterInvalid() { Validation<String, Integer> invalid = Validation.invalid("vavr"); assertThat(invalid.filter(i -> true).get()).isSameAs(invalid); assertThat(invalid.filter(i -> false).get()).isSameAs(invalid); } // -- flatMap @Test public void shouldFlatMapValid() { Validation<String, Integer> valid = Validation.valid(42); assertThat(valid.flatMap(v -> Validation.valid("ok")).get()).isEqualTo("ok"); } @Test public void shouldFlatMapInvalid() { Validation<String, Integer> invalid = Validation.invalid("vavr"); assertThat(invalid.flatMap(v -> Validation.valid("ok"))).isSameAs(invalid); } // -- orElse @Test public void shouldReturnSelfOnOrElseIfValid() { Validation<Seq<String>, String> validValidation = valid(); assertThat(validValidation.orElse(invalid())).isSameAs(validValidation); } @Test public void shouldReturnSelfOnOrElseSupplierIfValid() { Validation<Seq<String>, String> validValidation = valid(); assertThat(validValidation.orElse(() -> invalid())).isSameAs(validValidation); } @Test public void shouldReturnAlternativeOnOrElseIfValid() { Validation<Seq<String>, String> validValidation = valid(); assertThat(invalid().orElse(validValidation)).isSameAs(validValidation); } @Test public void shouldReturnAlternativeOnOrElseSupplierIfValid() { Validation<Seq<String>, String> validValidation = valid(); assertThat(invalid().orElse(() -> validValidation)).isSameAs(validValidation); } // -- getOrElseGet @Test public void shouldReturnValueOnGetOrElseGetIfValid() { Validation<Integer, String> validValidation = valid(); assertThat(validValidation.getOrElseGet(e -> "error" + e)).isEqualTo(OK); } @Test public void shouldReturnCalculationOnGetOrElseGetIfInvalid() { Validation<Integer, String> invalidValidation = Validation.invalid(42); assertThat(invalidValidation.getOrElseGet(e -> "error" + e)).isEqualTo("error42"); } // -- fold @Test public void shouldConvertSuccessToU() { Validation<Seq<String>, String> validValidation = valid(); Integer result = validValidation.fold(Seq::length, String::length); assertThat(result).isEqualTo(2); } @Test public void shouldConvertFailureToU() { Validation<Seq<String>, String> invalidValidation = invalid(); Integer result = invalidValidation.fold(Seq::length, String::length); assertThat(result).isEqualTo(3); } // -- swap @Test public void shouldSwapSuccessToFailure() { assertThat(valid().swap() instanceof Validation.Invalid).isTrue(); assertThat(valid().swap().getError()).isEqualTo(OK); } @Test public void shouldSwapFailureToSuccess() { assertThat(invalid().swap() instanceof Validation.Valid).isTrue(); assertThat(invalid().swap().get()).isEqualTo(ERRORS); } // -- map @Test public void shouldMapSuccessValue() { assertThat(valid().map(s -> s + "!").get()).isEqualTo(OK + "!"); } @Test public void shouldMapFailureError() { assertThat(invalid().map(s -> 2).getError()).isEqualTo(ERRORS); } @Test(expected = RuntimeException.class) public void shouldMapFailureErrorOnGet() { assertThat(invalid().map(s -> 2).get()).isEqualTo(ERRORS); } // -- bimap @Test public void shouldMapOnlySuccessValue() { Validation<Seq<String>, String> validValidation = valid(); Validation<Integer, Integer> validMapping = validValidation.bimap(Seq::length, String::length); assertThat(validMapping instanceof Validation.Valid).isTrue(); assertThat(validMapping.get()).isEqualTo(2); } @Test public void shouldMapOnlyFailureValue() { Validation<Seq<String>, String> invalidValidation = invalid(); Validation<Integer, Integer> invalidMapping = invalidValidation.bimap(Seq::length, String::length); assertThat(invalidMapping instanceof Validation.Invalid).isTrue(); assertThat(invalidMapping.getError()).isEqualTo(3); } // -- mapError @Test public void shouldNotMapSuccess() { assertThat(valid().mapError(x -> 2).get()).isEqualTo(OK); } @Test public void shouldMapFailure() { assertThat(invalid().mapError(x -> 5).getError()).isEqualTo(5); } // -- forEach @Test public void shouldProcessFunctionInForEach() { { // Valid.forEach java.util.List<String> accumulator = new ArrayList<>(); Validation<String, String> v1 = Validation.valid("valid"); v1.forEach(accumulator::add); assertThat(accumulator.size()).isEqualTo(1); assertThat(accumulator.get(0)).isEqualTo("valid"); } { // Invalid.forEach java.util.List<String> accumulator = new ArrayList<>(); Validation<String, String> v2 = Validation.invalid("error"); v2.forEach(accumulator::add); assertThat(accumulator.size()).isEqualTo(0); } } // -- combine and apply @Test public void shouldBuildUpForSuccessCombine() { Validation<String, String> v1 = Validation.valid("John Doe"); Validation<String, Integer> v2 = Validation.valid(39); Validation<String, Option<String>> v3 = Validation.valid(Option.of("address")); Validation<String, Option<String>> v4 = Validation.valid(Option.none()); Validation<String, String> v5 = Validation.valid("111-111-1111"); Validation<String, String> v6 = Validation.valid("alt1"); Validation<String, String> v7 = Validation.valid("alt2"); Validation<String, String> v8 = Validation.valid("alt3"); Validation<String, String> v9 = Validation.valid("alt4"); Validation<Seq<String>, TestValidation> result = v1.combine(v2).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result2 = v1.combine(v2).combine(v3).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result3 = v1.combine(v2).combine(v4).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result4 = v1.combine(v2).combine(v3).combine(v5).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result5 = v1.combine(v2).combine(v3).combine(v5).combine(v6).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result6 = v1.combine(v2).combine(v3).combine(v5).combine(v6).combine(v7).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result7 = v1.combine(v2).combine(v3).combine(v5).combine(v6).combine(v7).combine(v8).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result8 = v1.combine(v2).combine(v3).combine(v5).combine(v6).combine(v7).combine(v8).combine(v9).ap(TestValidation::new); Validation<Seq<String>, String> result9 = v1.combine(v2).combine(v3).ap((p1, p2, p3) -> p1 + ":" + p2 + ":" + p3.getOrElse("none")); assertThat(result.isValid()).isTrue(); assertThat(result2.isValid()).isTrue(); assertThat(result3.isValid()).isTrue(); assertThat(result4.isValid()).isTrue(); assertThat(result5.isValid()).isTrue(); assertThat(result6.isValid()).isTrue(); assertThat(result7.isValid()).isTrue(); assertThat(result8.isValid()).isTrue(); assertThat(result9.isValid()).isTrue(); assertThat(result.get() instanceof TestValidation).isTrue(); assertThat(result9.get() instanceof String).isTrue(); } @Test public void shouldBuildUpForSuccessMapN() { Validation<String, String> v1 = Validation.valid("John Doe"); Validation<String, Integer> v2 = Validation.valid(39); Validation<String, Option<String>> v3 = Validation.valid(Option.of("address")); Validation<String, Option<String>> v4 = Validation.valid(Option.none()); Validation<String, String> v5 = Validation.valid("111-111-1111"); Validation<String, String> v6 = Validation.valid("alt1"); Validation<String, String> v7 = Validation.valid("alt2"); Validation<String, String> v8 = Validation.valid("alt3"); Validation<String, String> v9 = Validation.valid("alt4"); // Alternative map(n) functions to the 'combine' function Validation<Seq<String>, TestValidation> result = Validation.combine(v1, v2).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result2 = Validation.combine(v1, v2, v3).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result3 = Validation.combine(v1, v2, v4).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result4 = Validation.combine(v1, v2, v3, v5).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result5 = Validation.combine(v1, v2, v3, v5, v6).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result6 = Validation.combine(v1, v2, v3, v5, v6, v7).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result7 = Validation.combine(v1, v2, v3, v5, v6, v7, v8).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result8 = Validation.combine(v1, v2, v3, v5, v6, v7, v8, v9).ap(TestValidation::new); Validation<Seq<String>, String> result9 = Validation.combine(v1, v2, v3).ap((p1, p2, p3) -> p1 + ":" + p2 + ":" + p3.getOrElse("none")); assertThat(result.isValid()).isTrue(); assertThat(result2.isValid()).isTrue(); assertThat(result3.isValid()).isTrue(); assertThat(result4.isValid()).isTrue(); assertThat(result5.isValid()).isTrue(); assertThat(result6.isValid()).isTrue(); assertThat(result7.isValid()).isTrue(); assertThat(result8.isValid()).isTrue(); assertThat(result9.isValid()).isTrue(); assertThat(result.get() instanceof TestValidation).isTrue(); assertThat(result9.get() instanceof String).isTrue(); } @Test public void shouldBuildUpForFailure() { Validation<String, String> v1 = Validation.valid("John Doe"); Validation<String, Integer> v2 = Validation.valid(39); Validation<String, Option<String>> v3 = Validation.valid(Option.of("address")); Validation<String, String> e1 = Validation.invalid("error2"); Validation<String, Integer> e2 = Validation.invalid("error1"); Validation<String, Option<String>> e3 = Validation.invalid("error3"); Validation<Seq<String>, TestValidation> result = v1.combine(e2).combine(v3).ap(TestValidation::new); Validation<Seq<String>, TestValidation> result2 = e1.combine(v2).combine(e3).ap(TestValidation::new); assertThat(result.isInvalid()).isTrue(); assertThat(result2.isInvalid()).isTrue(); } // -- miscellaneous @Test(expected = RuntimeException.class) public void shouldThrowErrorOnGetErrorValid() { Validation<String, String> v1 = valid(); v1.getError(); } @Test public void shouldMatchLikeObjects() { Validation<String, String> v1 = Validation.valid("test"); Validation<String, String> v2 = Validation.valid("test"); Validation<String, String> v3 = Validation.valid("test diff"); Validation<String, String> e1 = Validation.invalid("error1"); Validation<String, String> e2 = Validation.invalid("error1"); Validation<String, String> e3 = Validation.invalid("error diff"); assertThat(v1.equals(v1)).isTrue(); assertThat(v1.equals(v2)).isTrue(); assertThat(v1.equals(v3)).isFalse(); assertThat(e1.equals(e1)).isTrue(); assertThat(e1.equals(e2)).isTrue(); assertThat(e1.equals(e3)).isFalse(); } @Test public void shouldReturnCorrectStringForToString() { Validation<String, String> v1 = Validation.valid("test"); Validation<String, String> v2 = Validation.invalid("error"); assertThat(v1.toString()).isEqualTo("Valid(test)"); assertThat(v2.toString()).isEqualTo("Invalid(error)"); } @Test public void shouldReturnHashCode() { Validation<String, String> v1 = Validation.valid("test"); Validation<String, String> e1 = Validation.invalid("error"); assertThat(v1.hashCode()).isEqualTo(Objects.hashCode(v1)); assertThat(e1.hashCode()).isEqualTo(Objects.hashCode(e1)); } // ------------------------------------------------------------------------------------------ // private <E> Validation<E, String> valid() { return Validation.valid(OK); } private <T> Validation<Seq<String>, T> invalid() { return Validation.invalid(ERRORS); } public static class TestValidation { public String name; public Integer age; public Option<String> address; public String phone; public String alt1; public String alt2; public String alt3; public String alt4; public TestValidation(String name, Integer age) { this.name = name; this.age = age; address = Option.none(); } public TestValidation(String name, Integer age, Option<String> address) { this.name = name; this.age = age; this.address = address; } public TestValidation(String name, Integer age, Option<String> address, String phone) { this.name = name; this.age = age; this.address = address; this.phone = phone; } public TestValidation(String name, Integer age, Option<String> address, String phone, String alt1) { this.name = name; this.age = age; this.address = address; this.phone = phone; this.alt1 = alt1; } public TestValidation(String name, Integer age, Option<String> address, String phone, String alt1, String alt2) { this.name = name; this.age = age; this.address = address; this.phone = phone; this.alt1 = alt1; this.alt2 = alt2; } public TestValidation(String name, Integer age, Option<String> address, String phone, String alt1, String alt2, String alt3) { this.name = name; this.age = age; this.address = address; this.phone = phone; this.alt1 = alt1; this.alt2 = alt2; this.alt3 = alt3; } public TestValidation(String name, Integer age, Option<String> address, String phone, String alt1, String alt2, String alt3, String alt4) { this.name = name; this.age = age; this.address = address; this.phone = phone; this.alt1 = alt1; this.alt2 = alt2; this.alt3 = alt3; this.alt4 = alt4; } @Override public String toString() { return "TestValidation(" + name + "," + age + "," + address.getOrElse("none") + phone + "," + ")"; } } // -- Complete Validation example, may be moved to Vavr documentation later @Test public void shouldValidateValidPerson() { final String name = "John Doe"; final int age = 30; final Validation<Seq<String>, Person> actual = new PersonValidator().validatePerson(name, age); final Validation<Seq<String>, Person> expected = Validation.valid(new Person(name, age)); assertThat(actual).isEqualTo(expected); } @Test public void shouldValidateInvalidPerson() { final String name = "John? Doe!4"; final int age = -1; final Validation<Seq<String>, Person> actual = new PersonValidator().validatePerson(name, age); final Validation<Seq<String>, Person> expected = Validation.invalid(List.of( "Name contains invalid characters: '!4?'", "Age must be greater than 0" )); assertThat(actual).isEqualTo(expected); } static class PersonValidator { private final String validNameChars = "[a-zA-Z ]"; private final int minAge = 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(validNameChars, "").transform(seq -> seq.isEmpty() ? Validation.<String, String> valid(name) : Validation.<String, String> invalid("Name contains invalid characters: '" + seq.distinct().sorted() + "'")); } private Validation<String, Integer> validateAge(int age) { return (age < minAge) ? Validation.invalid("Age must be greater than 0") : Validation.valid(age); } } static class Person { public final String name; public final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof Person) { final Person person = (Person) o; return Objects.equals(name, person.name) && age == person.age; } else { return false; } } @Override public int hashCode() { return Objects.hash(name, age); } @Override public String toString() { return "Person(" + name + ", " + age + ")"; } } // -- spliterator @Test public void shouldHaveSizedSpliterator() { assertThat(of(1).spliterator().hasCharacteristics(Spliterator.SIZED | Spliterator.SUBSIZED)).isTrue(); } @Test public void shouldHaveOrderedSpliterator() { assertThat(of(1).spliterator().hasCharacteristics(Spliterator.ORDERED)).isTrue(); } @Test public void shouldReturnSizeWhenSpliterator() { assertThat(of(1).spliterator().getExactSizeIfKnown()).isEqualTo(1); } }