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}