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 * Standard implementations can also be acquired via these factory methods:
068 * <ul>
069 *   <li>{@link #withDefaults()}</li>
070 *   <li>{@link #withValueConverterRegistry(ValueConverterRegistry)}</li>
071 * </ul>
072 * <p>
073 * 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.
074 *
075 * @author <a href="https://www.revetkn.com">Mark Allen</a>
076 */
077@FunctionalInterface
078public interface RequestBodyMarshaler {
079        /**
080         * 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}.
081         * <p>
082         * This instance will be injected by Soklet when it invokes the <em>Resource Method</em> to handle the request.
083         *
084         * @param request         the request whose body should be converted into a Java type
085         * @param resourceMethod  the <em>Resource Method</em> that is configured to handle the request
086         * @param parameter       the <em>Resource Method</em> parameter into which the returned instance will be injected
087         * @param requestBodyType the type of the <em>Resource Method</em> parameter (provided for convenience)
088         * @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
089         */
090        @Nonnull
091        Optional<Object> marshalRequestBody(@Nonnull Request request,
092                                                                                                                                                        @Nonnull ResourceMethod resourceMethod,
093                                                                                                                                                        @Nonnull Parameter parameter,
094                                                                                                                                                        @Nonnull Type requestBodyType);
095
096        /**
097         * Acquires a basic {@link RequestBodyMarshaler} which knows how to convert request body data using {@link ValueConverterRegistry#sharedInstance()}.
098         * <p>
099         * You will likely want to provide your own implementation of {@link RequestBodyMarshaler} instead if your system accepts, for example, JSON request bodies.
100         * <p>
101         * Callers should not rely on reference identity; this method may return a new or cached instance.
102         *
103         * @return a {@code RequestBodyMarshaler} with default settings
104         */
105        @Nonnull
106        static RequestBodyMarshaler withDefaults() {
107                return DefaultRequestBodyMarshaler.defaultInstance();
108        }
109
110        /**
111         * Acquires a basic {@link RequestBodyMarshaler} which knows how to convert request body data using the provided {@link ValueConverterRegistry}.
112         * <p>
113         * You will likely want to provide your own implementation of {@link RequestBodyMarshaler} instead if your system accepts, for example, JSON request bodies.
114         * <p>
115         * Callers should not rely on reference identity; this method may return a new or cached instance.
116         *
117         * @param valueConverterRegistry a registry of converters that can transform {@link String} types to arbitrary Java types
118         * @return a default {@code RequestBodyMarshaler} backed by the given {@link ValueConverterRegistry}
119         */
120        @Nonnull
121        static RequestBodyMarshaler withValueConverterRegistry(@Nonnull ValueConverterRegistry valueConverterRegistry) {
122                requireNonNull(valueConverterRegistry);
123                return new DefaultRequestBodyMarshaler(valueConverterRegistry);
124        }
125}