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.core.impl;
018
019import com.soklet.annotation.RequestBody;
020import com.soklet.converter.ValueConversionException;
021import com.soklet.converter.ValueConverter;
022import com.soklet.converter.ValueConverterRegistry;
023import com.soklet.core.Request;
024import com.soklet.core.RequestBodyMarshaler;
025import com.soklet.core.ResourceMethod;
026import com.soklet.exception.IllegalRequestBodyException;
027
028import javax.annotation.Nonnull;
029import javax.annotation.concurrent.ThreadSafe;
030import java.lang.reflect.Parameter;
031import java.lang.reflect.Type;
032import java.util.Optional;
033
034import static java.lang.String.format;
035import static java.util.Objects.requireNonNull;
036
037/**
038 * @author <a href="https://www.revetkn.com">Mark Allen</a>
039 */
040@ThreadSafe
041public class DefaultRequestBodyMarshaler implements RequestBodyMarshaler {
042        @Nonnull
043        private static final DefaultRequestBodyMarshaler SHARED_INSTANCE;
044
045        static {
046                SHARED_INSTANCE = new DefaultRequestBodyMarshaler();
047        }
048
049        @Nonnull
050        private final ValueConverterRegistry valueConverterRegistry;
051
052        @Nonnull
053        public static DefaultRequestBodyMarshaler sharedInstance() {
054                return SHARED_INSTANCE;
055        }
056
057        public DefaultRequestBodyMarshaler() {
058                this(ValueConverterRegistry.sharedInstance());
059        }
060
061        public DefaultRequestBodyMarshaler(@Nonnull ValueConverterRegistry valueConverterRegistry) {
062                requireNonNull(valueConverterRegistry);
063                this.valueConverterRegistry = valueConverterRegistry;
064        }
065
066        @Nonnull
067        @Override
068        public Optional<Object> marshalRequestBody(@Nonnull Request request,
069                                                                                                                                                                                 @Nonnull ResourceMethod resourceMethod,
070                                                                                                                                                                                 @Nonnull Parameter parameter,
071                                                                                                                                                                                 @Nonnull Type requestBodyType) {
072                requireNonNull(request);
073                requireNonNull(resourceMethod);
074                requireNonNull(parameter);
075                requireNonNull(requestBodyType);
076
077                ValueConverter<Object, Object> valueConverter = getValueConverterRegistry().get(String.class, requestBodyType).orElse(null);
078
079                if (valueConverter == null)
080                        throw new IllegalStateException(format("Soklet is not configured to marshal @%s fields of type %s. "
081                                                        + "To do so, provide your own implementation of %s. See https://www.soklet.com/docs/request-handling#request-body for details.",
082                                        RequestBody.class.getSimpleName(), requestBodyType, RequestBodyMarshaler.class.getSimpleName()));
083
084                String requestBodyAsString = request.getBodyAsString().orElse(null);
085
086                try {
087                        if (requestBodyAsString == null)
088                                return Optional.empty();
089
090                        Optional<Object> valueConverterResult = valueConverter.convert(requestBodyAsString);
091                        return valueConverterResult == null ? Optional.empty() : valueConverterResult;
092                } catch (ValueConversionException e) {
093                        throw new IllegalRequestBodyException(format("Unable to marshal request body to %s", requestBodyType), e);
094                }
095        }
096
097        @Nonnull
098        protected ValueConverterRegistry getValueConverterRegistry() {
099                return this.valueConverterRegistry;
100        }
101}