All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.hamnaberg.json.codec.DecodeResult Maven / Gradle / Ivy

The newest version!
package net.hamnaberg.json.codec;

import net.hamnaberg.json.Json;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;

public abstract class DecodeResult {

    private DecodeResult() {
    }

    public final  DecodeResult map(Function f) {
        return fold(DecodeResult::fail, f.andThen(DecodeResult::ok));
    }

    public final DecodeResult filter(Predicate p) {
        return filter(p, () -> "Filter failed");
    }

    public final DecodeResult filter(Predicate p, Supplier errorSupplier) {
        return flatMap(a -> p.test(a) ? ok(a) : fail(errorSupplier.get()));
    }

    public final void forEach(Consumer f) {
        consume(ignore -> {}, f);
    }

    public final  DecodeResult flatMap(Function> f) {
        return fold(DecodeResult::fail, f);
    }

    public final A getOrElse(Supplier orElse) {
        return fold(ignore -> orElse.get(), Function.identity());
    }

    public final DecodeResult orElse(DecodeResult orThis) {
        return isOk() ? this : orThis;
    }

    public abstract  A getOrElseThrow(Function exProvider) throws X;

    public final A unsafeGet() {
        return fold(e -> {
            throw new NoSuchElementException(e);
        }, Function.identity());
    }

    public final Optional toOption() {
        return fold(ignore -> Optional.empty(), Optional::of);
    }

    public abstract  B fold(Function failFunction, Function okFunction);

    public abstract void consume(Consumer failFunction, Consumer okFunction);

    public boolean isOk() {
        return fold(a -> false, a -> true);
    }

    public boolean isFailure() {
        return fold(a -> true, a -> false);
    }

    public static  DecodeResult> sequence(List> decodeResults) {
        if (decodeResults.isEmpty()) {
            return DecodeResult.ok(List.of());
        }
        final List list = new ArrayList<>();
        final List errors = new ArrayList<>();

        for (DecodeResult res : decodeResults) {
            res.consume(errors::add, list::add);
        }
        if (errors.isEmpty()) {
            return DecodeResult.ok(List.copyOf(list));
        } else {
            return DecodeResult.fail("One or more results failed: " + String.join("\n", errors));
        }
    }

    public static  DecodeResult sequence(final Iterable> results, final Collector collector) {
        A accumulator = collector.supplier().get();
        for (final DecodeResult t : results) {
            if (t.isFailure()) {
                return fail(t.fold(Function.identity(), x -> {
                    throw new NoSuchElementException();
                }));
            }
            collector.accumulator().accept(accumulator, t.fold(f -> {
                throw new NoSuchElementException();
            }, Function.identity()));
        }
        return ok(collector.finisher().apply(accumulator));
    }

    public static  DecodeResult ok(A value) {
        return new Ok<>(value);
    }

    public static  DecodeResult fromOption(Optional value) {
        return fromOption(value, () -> "No value found");
    }

    public static  DecodeResult fromCallable(Callable value) {
        try {
            return ok(value.call());
        } catch (Exception e) {
            return fail(e.getMessage());
        }
    }

    public static  DecodeResult fromOption(Optional value, Supplier error) {
        return value.map(DecodeResult::ok).orElse(fail(error.get()));
    }

    @SuppressWarnings("unchecked")
    public static  DecodeResult fail(String message) {
        return (DecodeResult) new Failure(message);
    }


    private static DecodeResult getValue(Json.JObject object, String name) {
        return object.
                get(name).
                map(DecodeResult::ok).
                orElseGet(() -> DecodeResult.fail(String.format("'%s' not found in %s", name, object.nospaces())));
    }

    public static  DecodeResult decode(Json.JObject object, FieldDecoder decoder) {
        return decode(object, decoder.name, decoder.decoder);
    }

    public static  DecodeResult decode(Json.JObject object, String name, DecodeJson decoder) {
        DecodeResult result = getValue(object, name).flatMap(decoder::fromJson);
        if (result.isFailure()) {
            Optional defaultValue = decoder.defaultValue();
            result = defaultValue.map(DecodeResult::ok).orElse(result);
            if (result.isFailure()) {
                result = result.fold(
                        err -> DecodeResult.fail(err.contains(String.format("'%s' not found", name)) ? err : String.format("'%s' failed with message: '%s'", name, err)),
                        DecodeResult::ok
                );
            }
        }
        return result;
    }

    public final static class Ok extends DecodeResult {
        public final A value;

        public Ok(A value) {
            this.value = value;
        }

        @Override
        public  B fold(Function failFunction, Function okFunction) {
            return okFunction.apply(this.value);
        }

        @Override
        public void consume(Consumer failFunction, Consumer okFunction) {
            okFunction.accept(this.value);
        }

        @Override
        public  A getOrElseThrow(Function exProvider) throws X {
            return value;
        }

        public A getValue() {
            return value;
        }

        @Override
        public String toString() {
            return String.format("Ok(value='%s')", value);
        }
    }

    public final static class Failure extends DecodeResult {
        public final String message;

        public Failure(String message) {
            this.message = message;
        }

        @Override
        public  B fold(Function failFunction, Function okFunction) {
            return failFunction.apply(this.message);
        }

        @Override
        public  Object getOrElseThrow(Function exProvider) throws X {
            throw exProvider.apply(message);
        }

        @Override
        public void consume(Consumer failFunction, Consumer okFunction) {
            failFunction.accept(this.message);
        }

        @Override
        public String toString() {
            return String.format("Failure(message='%s')", message);
        }
    }

}