@Override public boolean matches(final NormalisedPath requestPath) { if (this.numberOfParts() != requestPath.numberOfParts()) { return false; } for (int i = 0; i < this.numberOfParts(); i++) { if (!this.partMatches(i, requestPath.part(i))) { return false; } } return true; }
/** * Tries to find the best fitting API path matching the given path and request method. * * @param path the requests path to find in API definition * @param method the {@link Request.Method} for the request * @return a {@link ApiOperationMatch} containing the information if the path is defined, the operation * is allowed and having the necessary {@link ApiOperation} if applicable */ @Nonnull public ApiOperationMatch findApiOperation(final String path, final Request.Method method) { // try to find possible matching paths regardless of HTTP method final NormalisedPath requestPath = new NormalisedPathImpl(path, apiPrefix); final List<ApiPath> matchingPaths = apiPathsGroupedByNumberOfParts .getOrDefault(requestPath.numberOfParts(), emptyList()).stream() .filter(p -> p.matches(requestPath)) .collect(toList()); if (matchingPaths.isEmpty()) { return ApiOperationMatch.MISSING_PATH; } // try to find the operation which fits the HTTP method, // choosing the most 'specific' path match from the candidates final PathItem.HttpMethod httpMethod = PathItem.HttpMethod.valueOf(method.name()); final Optional<ApiPath> matchingPathAndOperation = matchingPaths.stream() .filter(apiPath -> operations.contains(apiPath.original(), httpMethod)) .max(comparingInt(ApiOperationResolver::specificityScore)); return matchingPathAndOperation .map(match -> new ApiOperationMatch(new ApiOperation(match, requestPath, httpMethod, operations.get(match.original(), httpMethod)))) .orElse(ApiOperationMatch.NOT_ALLOWED_OPERATION); }
@Nonnull private ValidationReport validatePathParameters(final ApiOperation apiOperation) { ValidationReport validationReport = empty(); final NormalisedPath requestPath = apiOperation.getRequestPath(); for (int i = 0; i < apiOperation.getApiPath().numberOfParts(); i++) { if (!apiOperation.getApiPath().hasParams(i)) { continue; } final ValidationReport pathPartValidation = apiOperation .getApiPath() .paramValues(i, requestPath.part(i)) .entrySet() .stream() .map(param -> validatePathParameter(apiOperation, param.getKey(), param.getValue())) .reduce(empty(), ValidationReport::merge); validationReport = validationReport.merge(pathPartValidation); } return validationReport; }
/** * Validate the request against the given API operation * * @param request The request to validate * @param apiOperation The operation to validate the request against * * @return A validation report containing validation errors */ @Nonnull public ValidationReport validateRequest(final Request request, final ApiOperation apiOperation) { requireNonNull(request, "A request is required"); requireNonNull(apiOperation, "An API operation is required"); final MessageContext context = MessageContext.create() .in(REQUEST) .withApiOperation(apiOperation) .withRequestPath(apiOperation.getRequestPath().original()) .withRequestMethod(request.getMethod()) .build(); return securityValidator.validateSecurity(request, apiOperation) .merge(validateContentType(request, apiOperation)) .merge(validateAccepts(request, apiOperation)) .merge(validateHeaders(request, apiOperation)) .merge(validatePathParameters(apiOperation)) .merge(requestBodyValidator.validateRequestBody(request, apiOperation.getOperation().getRequestBody())) .merge(validateQueryParameters(request, apiOperation)) .withAdditionalContext(context); }