@Override
  public CompletableFuture<Void> onRequest(final FilterRequestContext requestContext)
  {
    // If the request requires validation on the response, build the validating schema now so that invalid projections
    // are spotted early
    if (shouldValidateOnResponse(requestContext))
    {
      MaskTree projectionMask = requestContext.getProjectionMask();
      if (projectionMask != null)
      {
        try
        {
          // Value class from resource model is the only source of truth for record schema.
          // Schema from the record template itself should not be used.
          DataSchema originalSchema = DataTemplateUtil.getSchema(requestContext.getFilterResourceModel().getValueClass());

          DataSchema validatingSchema = constructValidatingSchema(requestContext, originalSchema,
              projectionMask.getDataMap(), _nonSchemaFieldsToAllowInProjectionMask);

          // Put validating schema in scratchpad for use in onResponse
          requestContext.getFilterScratchpad().put(VALIDATING_SCHEMA_KEY, validatingSchema);
        }
        catch (InvalidProjectionException e)
        {
          throw new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, e.getMessage());
        }
        catch (TemplateRuntimeException e)
        {
          throw new RestLiServiceException(HttpStatus.S_500_INTERNAL_SERVER_ERROR, TEMPLATE_RUNTIME_EXCEPTION_MESSAGE);
        }
      }
    }

    if (!shouldValidateOnRequest(requestContext))
    {
      return CompletableFuture.completedFuture(null);
    }

    Class<?> resourceClass = requestContext.getFilterResourceModel().getResourceClass();
    if (UnstructuredDataUtil.isUnstructuredDataClass(resourceClass))
    {
      return CompletableFuture.completedFuture(null);
    }

    ResourceMethod method = requestContext.getMethodType();
    RestLiDataValidator validator = createRequestRestLiDataValidator(requestContext);
    RestLiRequestData requestData = requestContext.getRequestData();

    ValidationResult result;

    switch (method)
    {
      case CREATE:
      case UPDATE:
        result = validator.validateInput(requestData.getEntity());
        if (!result.isValid())
        {
          throw constructRestLiServiceException(result.getMessages(), result.getMessages().toString());
        }
        break;
      case PARTIAL_UPDATE:
        result = validator.validateInput((PatchRequest<?>) requestData.getEntity());
        if (!result.isValid())
        {
          throw constructRestLiServiceException(result.getMessages(), result.getMessages().toString());
        }
        break;
      case BATCH_CREATE:
        StringBuilder errorMessage = new StringBuilder();
        Map<String, Collection<Message>> messages = new HashMap<>();
        int index = 0;
        for (RecordTemplate entity : requestData.getBatchEntities())
        {
          result = validator.validateInput(entity);
          if (!result.isValid())
          {
            errorMessage.append("Index: ").append(index).append(", ").append(result.getMessages().toString());
            messages.put(String.valueOf(index), result.getMessages());
          }

          ++index;
        }

        if (errorMessage.length() > 0)
        {
          throw constructRestLiServiceException(messages, errorMessage.toString());
        }
        break;
      case BATCH_UPDATE:
      case BATCH_PARTIAL_UPDATE:
        ProtocolVersion protocolVersion = requestContext.getRestliProtocolVersion();
        StringBuilder stringBuilder = new StringBuilder();
        Map<String, Collection<Message>> errorMessages = new HashMap<>();
        for (Map.Entry<?, ? extends RecordTemplate> entry : requestData.getBatchKeyEntityMap().entrySet())
        {
          if (method == ResourceMethod.BATCH_UPDATE) {
            result = validator.validateInput(entry.getValue());
          }
          else
          {
            result = validator.validateInput((PatchRequest<?>) entry.getValue());
          }

          if (!result.isValid())
          {
            stringBuilder.append("Key: ").append(entry.getKey()).append(", ").append(result.getMessages().toString());
            errorMessages.put(URIParamUtils.encodeKeyForBody(entry.getKey(), false, protocolVersion), result.getMessages());
          }
        }

        if (stringBuilder.length() > 0)
        {
          throw constructRestLiServiceException(errorMessages, stringBuilder.toString());
        }
        break;
      default:
        break;
    }

    return CompletableFuture.completedFuture(null);
  }
@Test(dataProvider = "invalidRequests")
  @SuppressWarnings({"unchecked"})
  public void testSkipRequestValidation(ResourceModel resourceModel, ResourceMethod resourceMethod,
      RestLiRequestData restLiRequestData)
  {
    when(filterRequestContext.getRequestData()).thenReturn(restLiRequestData);
    when(filterRequestContext.getMethodType()).thenReturn(resourceMethod);
    when(filterRequestContext.getFilterResourceModel()).thenReturn(new FilterResourceModelImpl(resourceModel));
    when(filterRequestContext.getRestliProtocolVersion()).thenReturn(AllProtocolVersions.LATEST_PROTOCOL_VERSION);

    RestLiValidationFilter validationFilter =
        new RestLiValidationFilter(Collections.emptyList(), new MockValidationErrorHandler())
        {
          @Override
          protected boolean shouldValidateOnRequest(FilterRequestContext requestContext)
          {
            return false;
          }
        };

    try
    {
      validationFilter.onRequest(filterRequestContext);
    }
    catch (RestLiServiceException ex)
    {
      Assert.fail("An unexpected exception was thrown on request in the validation filter.", ex);
    }
  }