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}