001/*
002 * Copyright 2022-2025 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import com.soklet.converter.ValueConverterRegistry;
020
021import javax.annotation.Nonnull;
022import java.lang.reflect.Parameter;
023import java.lang.reflect.Type;
024import java.util.Optional;
025
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Contract for converting request body bytes into a corresponding Java type.
030 * <p>
031 * For example, if your <em>Resource Methods</em> expect JSON request bodies like this (note the {@link com.soklet.annotation.RequestBody} annotation):
032 * <pre>{@code  @POST("/find-biggest")
033 * public Integer findBiggest(@RequestBody List<Integer> numbers) {
034 *   // JSON request body [1,2,3] results in 3 being returned
035 *   return Collections.max(numbers);
036 * }}</pre>
037 * <p>
038 * You might implement a {@link RequestBodyMarshaler} to accept JSON like this:
039 * <pre>{@code  SokletConfig config = SokletConfig.withServer(
040 *   Server.withPort(8080).build()
041 * ).requestBodyMarshaler(new RequestBodyMarshaler() {
042 *   // This example uses Google's GSON
043 *   static final Gson GSON = new Gson();
044 *
045 *   @Nonnull
046 *   @Override
047 *   public Optional<Object> marshalRequestBody(
048 *     @Nonnull Request request,
049 *     @Nonnull ResourceMethod resourceMethod,
050 *     @Nonnull Parameter parameter,
051 *     @Nonnull Type requestBodyType
052 *   ) {
053 *     // Let GSON turn the request body into an instance
054 *     // of the specified type.
055 *     //
056 *     // Note that this method has access to all runtime information
057 *     // about the request, which provides the opportunity to, for example,
058 *     // examine annotations on the method/parameter which might
059 *     // inform custom marshaling strategies.
060 *     return Optional.of(GSON.fromJson(
061 *       request.getBodyAsString().get(),
062 *       requestBodyType
063 *     ));
064 *   }
065 * }).build();}</pre>
066 * <p>
067 * A standard threadsafe implementation can be acquired via the {@link #withValueConverterRegistry(ValueConverterRegistry)} factory method.
068 * <p>
069 * See <a href="https://www.soklet.com/docs/request-handling#request-body">https://www.soklet.com/docs/request-handling#request-body</a> for detailed documentation.
070 *
071 * @author <a href="https://www.revetkn.com">Mark Allen</a>
072 */
073@FunctionalInterface
074public interface RequestBodyMarshaler {
075        /**
076         * Given a request, the <em>Resource Method</em> that will handle it, and a {@link com.soklet.annotation.RequestBody}-annotated parameter + its type, convert the request body bytes into an instance of type {@code requestBodyType}.
077         * <p>
078         * This instance will be injected by Soklet when it invokes the <em>Resource Method</em> to handle the request.
079         *
080         * @param request         the request whose body should be converted into a Java type
081         * @param resourceMethod  the <em>Resource Method</em> that is configured to handle the request
082         * @param parameter       the <em>Resource Method</em> parameter into which the returned instance will be injected
083         * @param requestBodyType the type of the <em>Resource Method</em> parameter (provided for convenience)
084         * @return the Java instance that corresponds to the request body bytes suitable for assignment to the <em>Resource Method</em> parameter, or {@link Optional#empty()} if no instance should be marshaled
085         */
086        @Nonnull
087        Optional<Object> marshalRequestBody(@Nonnull Request request,
088                                                                                                                                                        @Nonnull ResourceMethod resourceMethod,
089                                                                                                                                                        @Nonnull Parameter parameter,
090                                                                                                                                                        @Nonnull Type requestBodyType);
091
092        /**
093         * Acquires a threadsafe {@link RequestBodyMarshaler} implementation which converts request body data using the provided {@link ValueConverterRegistry}.
094         * <p>
095         * You will likely want to provide your own implementation of {@link RequestBodyMarshaler} instead if your system accepts, for example, JSON request bodies.
096         * <p>
097         * Callers should not rely on reference identity; this method may return a new or cached instance.
098         *
099         * @param valueConverterRegistry a registry of converters that can transform {@link String} types to arbitrary Java types
100         * @return a default {@code RequestBodyMarshaler} backed by the given {@link ValueConverterRegistry}
101         */
102        @Nonnull
103        static RequestBodyMarshaler withValueConverterRegistry(@Nonnull ValueConverterRegistry valueConverterRegistry) {
104                requireNonNull(valueConverterRegistry);
105                return new DefaultRequestBodyMarshaler(valueConverterRegistry);
106        }
107}