Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@io7m
Created November 3, 2016 11:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save io7m/fd64fe18f9a94e83c1b64101e122aa1d to your computer and use it in GitHub Desktop.
Save io7m/fd64fe18f9a94e83c1b64101e122aa1d to your computer and use it in GitHub Desktop.
Validation example using entirely list-of-error Validations
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