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

io.vertx.openapi.validation.RequestUtils Maven / Gradle / Ivy

There is a newer version: 5.0.0.CR5
Show newest version
/*
 * Copyright (c) 2023, SAP SE
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 *
 */

package io.vertx.openapi.validation;

import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.openapi.contract.Operation;
import io.vertx.openapi.contract.Parameter;
import io.vertx.openapi.validation.impl.RequestParameterImpl;
import io.vertx.openapi.validation.impl.ValidatableRequestImpl;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static io.vertx.openapi.validation.ValidatorErrorType.ILLEGAL_VALUE;
import static java.util.stream.Collectors.joining;

public class RequestUtils {
  private static final RequestParameter EMPTY = new RequestParameterImpl(null);
  private static final Function, String> GET_FIRST_VALUE =
    values -> values.stream().findFirst().orElse(null);

  private RequestUtils() {

  }

  /**
   * Extracts and transforms the parameters and the body of an incoming request into a {@link ValidatableRequest format}
   * that can be validated by the {@link io.vertx.openapi.validation.RequestValidator}.
   *
   * @param request   the incoming request.
   * @param operation the operation of the related request.
   * @return A {@link Future} holding the ValidatableRequest.
   */
  public static Future extract(HttpServerRequest request, Operation operation) {
    return extract(request, operation, request::body);
  }

  /**
   * Like {@link #extract(HttpServerRequest, Operation)}, but offers to pass a supplier fpr the body. This is
   * helpful in case that the request has already been read.
   *
   * @param request      the incoming request.
   * @param operation    the operation of the related request.
   * @param bodySupplier the body supplier which can help in case that the request has already been read.
   * @return A {@link Future} holding the ValidatableRequest.
   */
  public static Future extract(HttpServerRequest request, Operation operation,
                                                   Supplier> bodySupplier) {
    Map cookies = new HashMap<>();
    Map headers = new HashMap<>();
    Map pathParams = new HashMap<>();
    Map query = new HashMap<>();

    for (Parameter param : operation.getParameters()) {
      switch (param.getIn()) {
        case COOKIE:
          cookies.put(param.getName(), extractCookie(request, param));
          break;
        case HEADER:
          headers.put(param.getName(), extractHeaders(request, param));
          break;
        case PATH:
          int segment = findPathSegment(operation.getAbsoluteOpenAPIPath(), param.getName());
          pathParams.put(param.getName(), extractPathParameters(request, segment));
          break;
        case QUERY:
          query.put(param.getName(), extractQuery(request, param));
      }
    }

    if (operation.getRequestBody() == null) {
      return Future.succeededFuture(new ValidatableRequestImpl(cookies, headers, pathParams, query));
    }

    String contentType = request.headers().get(HttpHeaders.CONTENT_TYPE);
    try {
      return bodySupplier.get().map(buffer -> {
        RequestParameter body = new RequestParameterImpl(buffer);
        return new ValidatableRequestImpl(cookies, headers, pathParams, query, body, contentType);
      });
    } catch (RuntimeException e) {
      return Future.failedFuture(e);
    }
  }

  private static RequestParameter extractCookie(HttpServerRequest request, Parameter parameter) {
    Collection cookies =
      request.cookies(parameter.getName()).stream().map(Cookie::getValue).collect(Collectors.toList());
    return joinFormValues(cookies, parameter, () -> {
      String explodedObject =
        request.cookies().stream().map(c -> c.getName() + "=" + decodeUrl(c.getValue())).collect(joining("&"));
      return new RequestParameterImpl(explodedObject);
    });
  }

  private static RequestParameter extractHeaders(HttpServerRequest request, Parameter parameter) {
    String headerValue = request.getHeader(parameter.getName());
    return new RequestParameterImpl(decodeUrl(headerValue));
  }

  private static RequestParameter extractPathParameters(HttpServerRequest request, int segment) {
    String[] pathSegments = request.path().substring(1).split("/");
    if (pathSegments.length < segment) {
      return EMPTY;
    }
    return new RequestParameterImpl(decodeUrl(pathSegments[segment - 1]));
  }

  private static RequestParameter extractQuery(HttpServerRequest request, Parameter parameter) {
    Collection queryParams = request.params().getAll(parameter.getName());
    return joinFormValues(queryParams, parameter, () -> {
      String decodedQuery =
        request.params().entries().stream().map(entry -> entry.getKey() + "=" + decodeUrl(entry.getValue()))
          .collect(joining("&"));
      return new RequestParameterImpl(decodedQuery);
    });
  }

  private static RequestParameter joinFormValues(Collection formValues, Parameter parameter,
                                                 Supplier explodedObjectSupplier) {
    if (formValues.isEmpty()) {
      return EMPTY;
    }

    switch (parameter.getSchemaType()) {
      case OBJECT:
        if (parameter.isExplode()) {
          return explodedObjectSupplier.get();
        } else {
          return new RequestParameterImpl(decodeUrl(GET_FIRST_VALUE.apply(formValues)));
        }
      case ARRAY:
        if (parameter.isExplode()) {
          String explodedString =
            formValues.stream().map(fv -> parameter.getName() + "=" + decodeUrl(fv)).collect(joining("&"));
          return new RequestParameterImpl(explodedString);
        } else {
          return new RequestParameterImpl(decodeUrl(GET_FIRST_VALUE.apply(formValues)));
        }
      default:
        return new RequestParameterImpl(decodeUrl(GET_FIRST_VALUE.apply(formValues)));
    }
  }

  // VisibleForTesting
  public static int findPathSegment(String templatePath, String parameterName) {
    int idx = templatePath.indexOf("{" + parameterName + "}");
    return (int) templatePath.subSequence(0, idx).chars().filter(c -> c == '/').count();
  }

  static String decodeUrl(String encoded) {
    try {
      return encoded == null ? null : URLDecoder.decode(encoded, StandardCharsets.UTF_8);
    } catch (Exception e) {
      throw new ValidatorException("Can't decode URL value: " + encoded, ILLEGAL_VALUE, e);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy