MessageValidator.java

/*
 * Copyright 2019 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.providence.util;

import net.morimekta.providence.PMessage;
import net.morimekta.providence.PMessageOrBuilder;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PMessageDescriptor;

import javax.annotation.Nonnull;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Class that handles validation of the structure or content of a message
 * type. This this can do much more fine grained validation than just assigning
 * required fields.
 *
 * @param <M> The message type to be validated.
 * @param <E> The exception to be thrown on validation failure.
 * @deprecated Use {@link MessageValidation}.
 */
@Deprecated
public class MessageValidator<
        M extends PMessage<M>,
        E extends Exception> {
    private final MessageValidation<M, E> validation;

    /**
     * Validate a message using the built expectations.
     *
     * @param message The message to be validated.
     * @throws E On not valid message.
     */
    @SuppressWarnings("unchecked")
    public void validate(PMessageOrBuilder<M> message) throws E {
        validation.validate(message);
    }

    /**
     * Just see if the message is valid or not. Does not
     *
     * @param message The message to be validated.
     * @return True if the message is valid, false otherwise.
     */
    @SuppressWarnings("unchecked")
    public boolean isValid(PMessageOrBuilder<M> message) {
        return validation.isValid(message);
    }

    /**
     * Just see if the message is valid or not. Does not
     *
     * @param message         The message to be validated.
     * @param messageConsumer Consumer of validation errors on the message.
     */
    @SuppressWarnings("unchecked")
    public void collectValidationErrors(PMessageOrBuilder<M> message, Consumer<String> messageConsumer) {
        try {
            validation.validate(message);
        } catch (Exception e) {
            messageConsumer.accept(e.getMessage());
        }
    }


    /**
     * Create a message validator that throws specific exception on failure.
     *
     * @param descriptor The message type descriptor to be validated.
     * @param onMismatch Function producer for thrown exceptions.
     * @param <M>        Message type.
     * @param <E>        Exception type.
     * @return The message validator builder.
     */
    public static <M extends PMessage<M>, E extends Exception>
    MessageValidator.Builder<M, E> builder(
            @Nonnull PMessageDescriptor<M> descriptor,
            @Nonnull Function<String, E> onMismatch) {
        return new Builder<>(descriptor, onMismatch);
    }

    /**
     * Builder vlass for message validators.
     *
     * @param <M> Message type.
     * @param <E> Exception type.
     * @deprecated Use {@link MessageValidation}.
     */
    @Deprecated
    public static class Builder<
            M extends PMessage<M>,
            E extends Exception> {
        /**
         * Build the validator.
         *
         * @return The validator instance.
         */
        @Nonnull
        public MessageValidator<M, E> build() {
            return new MessageValidator<>(this);
        }

        /**
         * Make a specific expectation for the message.
         *
         * @param text      The message text on expectation failure.
         * @param predicate Expectation predicate.
         * @return The builder instance.
         */
        @Nonnull
        public Builder<M, E> expect(@Nonnull String text,
                                    @Nonnull Predicate<M> predicate) {
            builder.expect(new MessageValidation.PredicateExpectation<>(predicate, text));
            return this;
        }

        /**
         * Given the field and type descriptor (which must match the field type),
         * build an inner validator to check the value of the field.
         *
         * @param field           The field to check.
         * @param descriptor      The message descriptor matching the field.
         * @param builderConsumer Consumer to configure the inner validator.
         * @param <M2>            The inner message type.
         * @return The builder instance.
         */
        @Nonnull
        public <M2 extends PMessage<M2>>
        Builder<M, E> expect(@Nonnull PField<M> field,
                             @Nonnull PMessageDescriptor<M2> descriptor,
                             @Nonnull Consumer<Builder<M2, E>> builderConsumer) {
            builder.expectIfPresent(field, descriptor, b2 -> {
                builderConsumer.accept(new Builder<>(b2));
            });
            return this;
        }

        /**
         * Expect the message to be non-null value.
         *
         * @return The builder instance.
         */
        @Nonnull
        public Builder<M, E> expectNotNull() {
            builder.expectNotNull();
            return this;
        }

        /**
         * Expect the message to be non-null value.
         *
         * @param text The failure message on null value.
         * @return The builder instance.
         */
        @Nonnull
        public Builder<M, E> expectNotNull(@Nonnull String text) {
            builder.expectNotNull();
            return this;
        }

        /**
         * Expect field to be present on message.
         *
         * @param fields The fields to be present.
         * @return The builder instance.
         */
        @Nonnull
        @SafeVarargs
        public final Builder<M, E> expectPresent(@Nonnull PField<M>... fields) {
            builder.expectPresent(fields);
            return this;
        }

        /**
         * Expect field to be present on message.
         *
         * @param text  The failure message on missing field.
         * @param field The field to be present.
         * @return The builder instance.
         */
        @Nonnull
        public Builder<M, E> expectPresent(@Nonnull String text, @Nonnull PField<M> field) {
            builder.expect(new MessageValidation.PredicateExpectation<>(message -> message.has(field), text));
            return this;
        }

        /**
         * Expect field to be present on message.
         *
         * @param fields The fields to be present.
         * @return The builder instance.
         */
        @Nonnull
        @SafeVarargs
        public final Builder<M, E> expectMissing(@Nonnull PField<M>... fields) {
            builder.expectMissing(fields);
            return this;
        }

        /**
         * Expect field to be present on message.
         *
         * @param text  The failure message on present field.
         * @param field The field to be present.
         * @return The builder instance.
         */
        @Nonnull
        public Builder<M, E> expectMissing(@Nonnull String text, @Nonnull PField<M> field) {
            builder.expect(new MessageValidation.PredicateExpectation<>(message -> !message.has(field), text));
            return this;
        }

        private Builder(PMessageDescriptor<M> descriptor, @Nonnull Function<String, E> onMismatch) {
            builder = MessageValidation.builder(descriptor, e -> onMismatch.apply(e.getMessage()));
        }
        private Builder(MessageValidation.Builder<M, E> builder) {
            this.builder = builder;
        }

        private final MessageValidation.Builder<M, E> builder;
    }

    private MessageValidator(Builder<M, E> builder) {
        validation = builder.builder.build();
    }
}