Skip to content

Instantly share code, notes, and snippets.

@io7m
Created November 3, 2016 11:36
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/8b8bf4c208e9a067a87786e8d8499097 to your computer and use it in GitHub Desktop.
Save io7m/8b8bf4c208e9a067a87786e8d8499097 to your computer and use it in GitHub Desktop.
Validation example using a mix of single-error Validations and 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 mixing {@link Validation}
* values that use both single error values and lists of error values.
*/
public final class ValidMix
{
private ValidMix()
{
}
static class Error
{
final String message;
Error(final String message)
{
this.message = message;
}
}
/**
* Attempt to parse an integer. It makes sense for this to return a
* Validation with a single error value, as the function attempts to
* parse a scalar and there's really only one thing that can go wrong.
*/
private static Validation<Error, ? extends Integer> parseInteger(
final String text)
{
try {
return valid(Integer.valueOf(Integer.parseInt(text)));
} catch (final NumberFormatException e) {
return invalid(new Error(e.getMessage()));
}
}
/**
* Attempt to parse a string value from a key. It makes sense for this to
* return a Validation with a single error value for the same reasons as
* {@link #parseInteger(String)}.
*/
private static Validation<Error, String> parseStringKey(
final Properties p,
final String key)
{
if (p.containsKey(key)) {
return valid(p.getProperty(key));
}
return invalid(new Error("No such key: " + key));
}
/**
* Attempt to parse an integer value from a key. It makes sense for this to
* return a Validation with a single error value for the same reasons as
* {@link #parseInteger(String)}.
*/
private static Validation<Error, Integer> parseIntegerKey(
final Properties p,
final String key)
{
return parseStringKey(p, key).flatMap(ValidMix::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)
{
/*
* Note the use of `unflatten` to get from a single error to a list of
* errors...
*/
return unflatten(parseStringKey(p, key)).flatMap(
text -> {
final List<Validation<Error, ? extends Integer>> integers =
List.of(text.split("\\s+"))
.map(Function1.of(String::trim)
.andThen(Function1.of(ValidMix::parseInteger)));
final List<Validation<Error, ? extends Integer>> invalids =
integers.filter(Validation::isInvalid);
if (!invalids.isEmpty()) {
return invalid(integers.map(Validation::getError));
}
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 unflatten(parseStringKey(p, key)).flatMap(
text -> {
final List<Validation<Error, String>> strings =
List.of(text.split("\\s+"))
.map(Function1.of(String::trim).andThen(
segment -> {
if (segment.isEmpty()) {
return invalid(new Error("Empty string"));
}
return valid(segment);
}));
final List<Validation<Error, String>> invalids =
strings.filter(Validation::isInvalid);
if (!invalids.isEmpty()) {
return invalid(strings.map(Validation::getError));
}
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 mix of Validation values
* that have single Error values and 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(
unflatten(parseIntegerKey(p, "x")),
unflatten(parseIntegerKey(p, "y")),
parseListInteger(p, "zs"),
unflatten(parseStringKey(p, "a")),
unflatten(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));
}
private static <E, T> Validation<List<E>, T> unflatten(
final Validation<E, T> v)
{
return v.leftMap(List::of);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment