All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.atlassian.oai.validator.springmvc.OpenApiValidationService Maven / Gradle / Ivy

There is a newer version: 2.43.0
Show newest version
package com.atlassian.oai.validator.springmvc;

import com.atlassian.oai.validator.OpenApiInteractionValidator;
import com.atlassian.oai.validator.model.Request;
import com.atlassian.oai.validator.model.Response;
import com.atlassian.oai.validator.model.SimpleRequest;
import com.atlassian.oai.validator.model.SimpleResponse;
import com.atlassian.oai.validator.report.ValidationReport;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;

class OpenApiValidationService {

    private static final List URI_CHARSETS = resolveAvailableCharsets();
    private final OpenApiInteractionValidator validator;
    private final UrlPathHelper urlPathHelper;

    OpenApiValidationService(final EncodedResource specAsResource, final UrlPathHelper urlPathHelper) throws IOException {
        this(OpenApiInteractionValidator
                .createForInlineApiSpecification(readReader(specAsResource.getReader(), -1))
                .withLevelResolver(SpringMVCLevelResolverFactory.create())
                .build(), urlPathHelper);
    }

    OpenApiValidationService(final OpenApiInteractionValidator validator, final UrlPathHelper urlPathHelper) {
        requireNonNull(validator, "An OpenAPI validator is required.");
        this.validator = validator;
        this.urlPathHelper = urlPathHelper;
    }

    private static List resolveAvailableCharsets() {
        final List uriCharsets = Charset.availableCharsets().values().stream()
                .map(Charset::name)
                .collect(Collectors.toList());
        // put UTF-8 to the front as it is the most probable charset for the URI encoding
        uriCharsets.remove(StandardCharsets.UTF_8.name());
        uriCharsets.add(0, StandardCharsets.UTF_8.name());
        return uriCharsets;
    }

    private String resolveServletPath(final HttpServletRequest servletRequest) {
        // The method HttpServletRequest#getServletPath might return NULL even in case there is an actual
        // servlet path. The UrlPathHelper is helping getting the servlet path.
        return urlPathHelper.getPathWithinApplication(servletRequest);
    }

    /**
     * @param servletRequest the {@link HttpServletRequest}
     *
     * @return the build {@link Request} created out of given {@link HttpServletRequest}
     *
     * @throws IOException if the request body can't be read
     */
    Request buildRequest(final HttpServletRequest servletRequest) throws IOException {
        requireNonNull(servletRequest, "A request is required.");

        final Request.Method method = Request.Method.valueOf(servletRequest.getMethod());
        final String path = resolveServletPath(servletRequest);
        final SimpleRequest.Builder builder = new SimpleRequest.Builder(method, path);
        final int contentLength = servletRequest.getContentLength();
        final String body = readReader(servletRequest.getReader(), contentLength);
        // The content length of a request does not need to be set. It might by "-1" and
        // there is still a body. Only in conjunction with an empty / unset body it was
        // really empty.
        if (contentLength >= 0 || StringUtils.isNotEmpty(body)) {
            builder.withBody(body);
        }
        for (final String queryParameterName : getQueryParameterNames(servletRequest)) {
            builder.withQueryParam(queryParameterName, servletRequest.getParameterValues(queryParameterName));
        }
        for (final String headerName : Collections.list(servletRequest.getHeaderNames())) {
            builder.withHeader(headerName, Collections.list(servletRequest.getHeaders(headerName)));
        }
        return builder.build();
    }

    /**
     * @param servletResponse the {@link javax.servlet.http.HttpServletResponse} whose body is cached
     *
     * @return the build {@link Response} created out of given {@link ContentCachingResponseWrapper}
     *
     * @throws IOException if the cached response body can't be read
     */
    Response buildResponse(final ContentCachingResponseWrapper servletResponse) throws IOException {
        final int statusCode = servletResponse.getStatusCode();
        final SimpleResponse.Builder builder =
                new SimpleResponse.Builder(statusCode)
                        .withBody(new String(servletResponse.getContentAsByteArray(), servletResponse.getCharacterEncoding()))
                        .withContentType(servletResponse.getContentType());
        for (final String headerName : servletResponse.getHeaderNames()) {
            builder.withHeader(headerName, Lists.newArrayList(servletResponse.getHeaders(headerName)));
        }

        return builder.build();
    }

    /**
     * @param request the {@link Request} to validate against the OpenAPI / Swagger specification
     *
     * @return the {@link ValidationReport} for the validated {@link Request}
     */
    ValidationReport validateRequest(final Request request) {
        return validator.validateRequest(request);
    }

    /**
     * @param servletRequest the {@link HttpServletRequest} to examine the api path to validate against
     * @param response the {@link Response} to validate against the OpenAPI / Swagger specification
     *
     * @return the {@link ValidationReport} for the validated {@link Request}
     */
    ValidationReport validateResponse(final HttpServletRequest servletRequest,
                                      final Response response) {
        final Request.Method method = Request.Method.valueOf(servletRequest.getMethod());
        final String path = resolveServletPath(servletRequest);
        return validator.validateResponse(path, method, response);
    }

    private static String readReader(final Reader reader, final int length) throws IOException {
        try (Reader reassignedReader = reader) {
            final StringWriter writer = length > 0 ? new StringWriter(length) : new StringWriter();
            IOUtils.copy(reassignedReader, writer);
            return writer.toString();
        }
    }

    private static Set getQueryParameterNames(final HttpServletRequest servletRequest) {
        final List allAvailableNames = Collections.list(servletRequest.getParameterNames());
        return Stream.of(StringUtils.split(StringUtils.defaultIfBlank(servletRequest.getQueryString(), ""), "&"))
                .map(str -> uriDecodeParamName(allAvailableNames, StringUtils.split(str, "=")[0]))
                .filter(StringUtils::isNotEmpty)
                .collect(Collectors.toSet());
    }

    private static String uriDecodeParamName(final List allParameterNames, final String paramName) {
        // It is difficult to get the correct uri encoding. Ideally it does not need decoding. And if it's encoded better
        // with UTF-8 as charset.
        // Beyond that it's guessing. It can be verified by checking the decoded name with all available parameter names.
        if (!allParameterNames.contains(paramName)) {
            for (final String charset : URI_CHARSETS) {
                final String decoded = uriDecode(paramName, charset);
                if (allParameterNames.contains(decoded)) {
                    return decoded;
                }
            }
        }
        return paramName;
    }

    private static String uriDecode(final String paramName, final String charset) {
        try {
            return URLDecoder.decode(paramName, charset);
        } catch (final UnsupportedEncodingException e) {
            // should not happen as only supported charsets will be used
            return paramName;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy