Created
November 3, 2016 11:37
-
-
Save io7m/fd64fe18f9a94e83c1b64101e122aa1d to your computer and use it in GitHub Desktop.
Validation example using entirely list-of-error Validations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javaslang.Function1; | |
import javaslang.collection.List; | |
import javaslang.control.Validation; | |
import java.util.Properties; | |
import static javaslang.control.Validation.invalid; | |
import static javaslang.control.Validation.valid; | |
/** | |
* An example to demonstrate what tends to happen when using {@link Validation} | |
* values that use lists of error values. | |
*/ | |
public final class ValidListsEverywhere | |
{ | |
private ValidListsEverywhere() | |
{ | |
} | |
static class Error | |
{ | |
final String message; | |
Error(final String message) | |
{ | |
this.message = message; | |
} | |
} | |
/** | |
* Attempt to parse an integer. It doesn't really make sense for this to | |
* return a Validation with a list of error values, but we're going to do it | |
* anyway because it *might* make the values easier to compose later. | |
*/ | |
private static Validation<List<Error>, ? extends Integer> parseInteger( | |
final String text) | |
{ | |
try { | |
return valid(Integer.valueOf(Integer.parseInt(text))); | |
} catch (final NumberFormatException e) { | |
return invalid(List.of(new Error(e.getMessage()))); | |
} | |
} | |
/** | |
* Attempt to parse a string value from a key. It doesn't really make sense | |
* for this to return a Validation with a list of error values, but we're | |
* going to do it anyway because it *might* make the values easier to compose | |
* later. | |
*/ | |
private static Validation<List<Error>, String> parseStringKey( | |
final Properties p, | |
final String key) | |
{ | |
if (p.containsKey(key)) { | |
return valid(p.getProperty(key)); | |
} | |
return invalid(List.of(new Error("No such key: " + key))); | |
} | |
/** | |
* Attempt to parse an integer value from a key. It doesn't really make sense | |
* for this to return a Validation with a list of error values, but we're | |
* going to do it anyway because it *might* make the values easier to compose | |
* later. | |
*/ | |
private static Validation<List<Error>, Integer> parseIntegerKey( | |
final Properties p, | |
final String key) | |
{ | |
return parseStringKey(p, key).flatMap(ValidListsEverywhere::parseInteger); | |
} | |
/** | |
* Attempt to parse a list of integer values from a key. It makes sense for | |
* this to return a Validation with a list of error values, as each individual | |
* integer value may fail to parse. | |
*/ | |
private static Validation<List<Error>, List<Integer>> parseListInteger( | |
final Properties p, | |
final String key) | |
{ | |
return parseStringKey(p, key).flatMap( | |
text -> { | |
final List<Validation<List<Error>, ? extends Integer>> integers = | |
List.of(text.split("\\s+")) | |
.map(Function1.of(String::trim) | |
.andThen(Function1.of(ValidListsEverywhere::parseInteger))); | |
final List<Validation<List<Error>, ? extends Integer>> invalids = | |
integers.filter(Validation::isInvalid); | |
/* | |
* Naturally, validating a list of integers here will result in | |
* a list of lists of validation values, so we have to flatten those | |
* down in order to return them... | |
*/ | |
if (!invalids.isEmpty()) { | |
return invalid(integers | |
.map(Validation::getError) | |
.fold(List.empty(), List::appendAll)); | |
} | |
return valid(integers.map(Validation::get)); | |
}); | |
} | |
/** | |
* Attempt to parse a list of string values from a key. It makes sense for | |
* this to return a Validation with a list of error values, as each individual | |
* string value may fail to parse. | |
*/ | |
private static Validation<List<Error>, List<String>> parseListString( | |
final Properties p, | |
final String key) | |
{ | |
return parseStringKey(p, key).flatMap( | |
text -> { | |
final List<Validation<List<Error>, String>> strings = | |
List.of(text.split("\\s+")) | |
.map(Function1.of(String::trim).andThen(segment -> { | |
if (segment.isEmpty()) { | |
return invalid(List.of(new Error("Empty string"))); | |
} | |
return valid(segment); | |
})); | |
final List<Validation<List<Error>, String>> invalids = | |
strings.filter(Validation::isInvalid); | |
if (!invalids.isEmpty()) { | |
return invalid(strings | |
.map(Validation::getError) | |
.fold(List.empty(), List::appendAll)); | |
} | |
return valid(strings.map(Validation::get)); | |
}); | |
} | |
static class Data | |
{ | |
Integer x; | |
Integer y; | |
List<Integer> zs; | |
String a; | |
String b; | |
List<String> cs; | |
public Data( | |
final Integer x, | |
final Integer y, | |
final List<Integer> zs, | |
final String a, | |
final String b, | |
final List<String> cs) | |
{ | |
this.x = x; | |
this.y = y; | |
this.zs = zs; | |
this.a = a; | |
this.b = b; | |
this.cs = cs; | |
} | |
} | |
/** | |
* Parse a configuration. Note that now we have a set of Validation values | |
* that have lists of Error values. The {@link | |
* Validation#ap(Validation)} function will yield {@code | |
* Validation<List<List<Error>>, Data>} and must be flattened down to {@code | |
* Validation<List<Error>>, Data>}. | |
*/ | |
static Validation<List<Error>, Data> parseData( | |
final Properties p) | |
{ | |
return flatten(Validation.combine( | |
parseIntegerKey(p, "x"), | |
parseIntegerKey(p, "y"), | |
parseListInteger(p, "zs"), | |
parseStringKey(p, "a"), | |
parseStringKey(p, "b"), | |
parseListString(p, "cs")).ap(Data::new)); | |
} | |
private static <E, T> Validation<List<E>, T> flatten( | |
final Validation<List<List<E>>, T> v) | |
{ | |
return v.leftMap(xs -> xs.fold(List.empty(), List::appendAll)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment