ArgumentBinder.java

/*
 * Copyright 2017-2020 original authors
 *
 * Licensed 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
 *
 * https://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 io.micronaut.core.bind;

import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionError;
import io.micronaut.core.type.Argument;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

/**
 * <p>An interface capable of binding the value of an {@link io.micronaut.core.type.Argument} from a source</p>.
 *
 * <p>The selection of an {@link ArgumentBinder} is done by the {@link ArgumentBinderRegistry}. Selection could
 * be based on type, annotation or other factors such as the request media type</p>
 *
 * <p>Unlike {@link io.micronaut.core.convert.TypeConverter} instances binders can potentially handle complex
 * objects and typically work on conjunction with a {@link io.micronaut.core.convert.value.ConvertibleValues} instance</p>
 *
 * <p>An {@link ArgumentBinder} can either be registered as a bean or by META-INF/services. In the case of the latter
 * it will be globally available at all times, whilst the former is only present when a {@code io.micronaut.context.BeanContext} is initialized</p>
 *
 * @param <T> The argument type
 * @param <S> The source type
 * @author Graeme Rocher
 * @see io.micronaut.core.convert.TypeConverter
 * @see io.micronaut.core.convert.value.ConvertibleValues
 * @since 1.0
 */
@FunctionalInterface
public interface ArgumentBinder<T, S> {

    /**
     * Create a specific binder.
     *
     * @param argument The bound argument
     * @return The specific binder
     * @since 4.8
     */
    @NonNull
    default ArgumentBinder<T, S> createSpecific(@NonNull Argument<T> argument) {
        return this;
    }

    /**
     * Bind the given argument from the given source.
     *
     * @param context The {@link ArgumentConversionContext}
     * @param source  The source
     * @return An {@link Optional} of the value. If no binding was possible {@link Optional#empty()}
     */
    BindingResult<T> bind(ArgumentConversionContext<T> context, S source);

    /**
     * The result of binding.
     *
     * @param <T>
     */
    interface BindingResult<T> {

        /**
         * An empty but satisfied result.
         */
        BindingResult EMPTY = Optional::empty;

        /**
         * An empty but unsatisfied result.
         */
        BindingResult UNSATISFIED = new BindingResult() {
            @Override
            public Optional getValue() {
                return Optional.empty();
            }

            @Override
            public boolean isSatisfied() {
                return false;
            }

            @Override
            public BindingResult flatMap(Function transform) {
                return this;
            }
        };

        /**
         * @return The bound value
         */
        Optional<T> getValue();

        /**
         * @return The {@link ConversionError} instances that occurred
         */
        default List<ConversionError> getConversionErrors() {
            return Collections.emptyList();
        }

        /**
         * @return Was the binding requirement satisfied
         */
        default boolean isSatisfied() {
            return getConversionErrors().isEmpty();
        }

        /**
         * @return Is the value present and satisfied
         */
        default boolean isPresentAndSatisfied() {
            return isSatisfied() && getValue().isPresent();
        }

        /**
         * Obtains the value. Callers should call {@link #isPresentAndSatisfied()} first.
         *
         * @return The value
         */
        @SuppressWarnings({"java:S3655", "OptionalGetWithoutIsPresent"})
        default T get() {
            return getValue().get();
        }

        /**
         * Transform the result, if present.
         *
         * @param transform The transformation function
         * @param <R>       The type of the mapped result
         * @return The mapped result
         * @since 4.0.0
         */
        @Experimental
        @NonNull
        default <R> BindingResult<R> flatMap(@NonNull Function<T, BindingResult<R>> transform) {
            return new MappedBindingResult<>(this, transform);
        }

        /**
         * @param <R> The result type
         * @return An empty but satisfied result.
         * @since 4.0.0
         */
        static <R> BindingResult<R> empty() {
            return BindingResult.EMPTY;
        }

        /**
         * @param <R> The result type
         * @return An empty but unsatisfied result.
         * @since 4.0.0
         */
        static <R> BindingResult<R> unsatisfied() {
            return UNSATISFIED;
        }
    }
}