001package org.consensusj.jsonrpc.cli;
002
003import com.fasterxml.jackson.core.JsonProcessingException;
004import com.fasterxml.jackson.databind.JsonNode;
005
006import java.util.stream.Stream;
007
008/**
009 * JSON-RPC method parameter, parsed from the command-line. There are two implementations: {@link Valid} and {@link Invalid}.
010 * A {@code Valid} contains an object that can be serialized to JSON via a converter/mapper like Jackson. An {@code Invalid}
011 * contains the {@link Exception} that occurred while parsing.
012 */
013public sealed interface CliParameter {
014    /**
015     * Return the source (pre-deserialization) JSON string
016     * @return source string
017     */
018    String source();
019
020    default boolean valid() {
021        return this instanceof CliParameter.Valid;
022    }
023
024    default boolean invalid() {
025        return !valid();
026    }
027
028    /**
029     * Stream zero or 1 valid objects.
030     * <p>This allows {@code .flatMap(CliParameter::stream)} to filter invalid and unwrap valid objects.
031     * @return A stream of zero or one valid objects.
032     */
033    Stream<Object> stream();
034
035    /**
036     * Create a {@code Valid} object
037     * @param source The source CLI string
038     * @param object The resulting object
039     * @return a valid parameter
040     */
041    static Valid valid(String source, Object object) {
042        return new Valid(source, object);
043    }
044
045    /**
046     * Create a {@code Invalid} object
047     * @param source The source CLI string
048     * @param error The error returned from the parser
049     * @return a valid parameter
050     */
051    static Invalid invalid(String source, Exception error) {
052        return new Invalid(source, error);
053    }
054
055    /**
056     * Parse a string returning either a valid or invalid {@code CliParameter}
057     * @param source the CLI parameter string to parse
058     * @param parseFunction a parsing function
059     * @return wraps either parsed, serializable object or an {@code Exception}
060     */
061    static CliParameter parse(String source, Parser parseFunction) {
062        try {
063            return new CliParameter.Valid(source, parseFunction.apply(source));
064        } catch (Exception e) {
065            return new CliParameter.Invalid(source, e);
066        }
067    }
068
069    /**
070     * Implementation for Valid objects
071     * @param source source JSON string
072     * @param object parsed object
073     */
074    record Valid(String source, Object object) implements CliParameter {
075        public Stream<Object> stream() { return Stream.of(object); }
076    }
077
078    /**
079     * Implementation for Invalid source strings
080     * @param source source JSON string
081     * @param error parsing exception
082     */
083    record Invalid(String source, Exception error) implements CliParameter {
084        public Stream<Object> stream() { return Stream.empty(); }
085    }
086
087    /**
088     * Functional interface for parsing a {@link String} to an {@link Object} possibly
089     * throwing an {@link Exception}.
090     */
091    @FunctionalInterface
092    interface Parser {
093        JsonNode apply(String s) throws JsonProcessingException;
094    }
095}