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

org.mockserver.matchers.HttpRequestsPropertiesMatcher Maven / Gradle / Ivy

There is a newer version: 5.15.0
Show newest version
package org.mockserver.matchers;

import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.common.base.Joiner;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Encoding;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.mockserver.log.model.LogEntry;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.*;
import org.mockserver.openapi.OpenAPISerialiser;
import org.mockserver.openapi.examples.JsonNodeExampleSerializer;
import org.mockserver.serialization.ObjectMapperFactory;
import org.slf4j.event.Level;

import java.io.IOException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mockserver.model.JsonSchemaBody.jsonSchema;
import static org.mockserver.model.NottableOptionalString.optionalString;
import static org.mockserver.model.NottableSchemaString.schemaString;
import static org.mockserver.model.NottableString.string;
import static org.mockserver.model.ParameterStyle.*;
import static org.mockserver.openapi.OpenAPISerialiser.OPEN_API_LOAD_ERROR;
import static org.slf4j.event.Level.ERROR;
import static org.slf4j.event.Level.TRACE;

public class HttpRequestsPropertiesMatcher extends AbstractHttpRequestMatcher {

    private static final ObjectWriter TO_STRING_OBJECT_WRITER = ObjectMapperFactory.createObjectMapper(true);
    private int hashCode;
    private OpenAPIDefinition openAPIDefinition;
    private List httpRequestPropertiesMatchers;
    private List httpRequests;
    private static final ObjectWriter OBJECT_WRITER = ObjectMapperFactory.createObjectMapper(new JsonNodeExampleSerializer()).writerWithDefaultPrettyPrinter();

    protected HttpRequestsPropertiesMatcher(MockServerLogger mockServerLogger) {
        super(mockServerLogger);
    }

    public List getHttpRequestPropertiesMatchers() {
        return httpRequestPropertiesMatchers;
    }

    @Override
    public List getHttpRequests() {
        return httpRequests;
    }

    @Override
    public boolean apply(RequestDefinition requestDefinition) {
        OpenAPIDefinition openAPIDefinition = requestDefinition instanceof OpenAPIDefinition ? (OpenAPIDefinition) requestDefinition : null;
        if (this.openAPIDefinition == null || !this.openAPIDefinition.equals(openAPIDefinition)) {
            this.openAPIDefinition = openAPIDefinition;
            if (openAPIDefinition != null && isNotBlank(openAPIDefinition.getSpecUrlOrPayload())) {
                httpRequestPropertiesMatchers = new ArrayList<>();
                httpRequests = new ArrayList<>();
                OpenAPISerialiser openAPISerialiser = new OpenAPISerialiser(mockServerLogger);
                try {
                    OpenAPI openAPI = openAPISerialiser.buildOpenAPI(openAPIDefinition.getSpecUrlOrPayload(), true);
                    final Map>> stringListMap = openAPISerialiser.retrieveOperations(openAPI, openAPIDefinition.getOperationId());
                    stringListMap
                        .forEach((path, operations) -> operations
                            .forEach(methodOperationPair -> {
                                Operation operation = methodOperationPair.getValue();
                                if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) {
                                    operation.getRequestBody().getContent().forEach(handleRequestBody(openAPIDefinition, openAPI, path, methodOperationPair, Boolean.TRUE.equals(operation.getRequestBody().getRequired())));
                                } else {
                                    HttpRequest httpRequest = createHttpRequest(openAPIDefinition, openAPI, path, methodOperationPair);
                                    addRequestMatcher(openAPIDefinition, methodOperationPair, httpRequest, "");
                                }
                            }));
                } catch (Throwable throwable) {
                    String message = (StringUtils.isBlank(throwable.getMessage()) || !throwable.getMessage().contains(OPEN_API_LOAD_ERROR) ? OPEN_API_LOAD_ERROR + (isNotBlank(throwable.getMessage()) ? ", " : "") : "") + throwable.getMessage();
                    throw new IllegalArgumentException(message, throwable);
                }
            }
            this.hashCode = 0;
            if (MockServerLogger.isEnabled(TRACE)) {
                mockServerLogger.logEvent(
                    new LogEntry()
                        .setLogLevel(TRACE)
                        .setHttpRequest(requestDefinition)
                        .setMessageFormat("created request matcher{}for open api definition{}")
                        .setArguments(this, requestDefinition)
                );
            }
            return true;
        } else {
            return false;
        }
    }

    private HttpRequest createHttpRequest(OpenAPIDefinition openAPIDefinition, OpenAPI openAPI, String path, Pair methodOperationPair) {
        HttpRequest httpRequest = new HttpRequest()
            .withMethod(methodOperationPair.getKey())
            .withPath(path);
        Operation operation = methodOperationPair.getValue();
        // parameters
        if (operation.getParameters() != null) {
            for (Parameter parameter : operation.getParameters()) {
                Schema schema = Optional
                    .ofNullable(parameter.getSchema())
                    .orElse(Optional
                        .ofNullable(parameter.getContent())
                        .flatMap(content -> content
                            .values()
                            .stream()
                            .map(MediaType::getSchema)
                            .findFirst()
                        )
                        .orElse(null)
                    );
                if (schema != null) {
                    try {
                        NottableString name = (parameter.getRequired() != null && parameter.getRequired() ? string(parameter.getName()) : optionalString(parameter.getName())).withStyle(parameterStyle(parameter.getExplode(), parameter.getStyle()));
                        if (parameter.getAllowEmptyValue() != null && parameter.getAllowEmptyValue()) {
                            schema.nullable(true);
                        }
                        if (Boolean.TRUE.equals(parameter.getAllowReserved())) {
                            throw new IllegalArgumentException("allowReserved field is not supported on parameters, found on operation: \"" + methodOperationPair.getRight().getOperationId() + "\" method: \"" + methodOperationPair.getLeft() + "\" parameter: \"" + name + "\" in: \"" + parameter.getIn() + "\"");
                        }
                        switch (parameter.getIn()) {
                            case "path": {
                                httpRequest.withPathParameter(name, schemaString(OBJECT_WRITER.writeValueAsString(schema)));
                                break;
                            }
                            case "query": {
                                httpRequest.withQueryStringParameter(name, schemaString(OBJECT_WRITER.writeValueAsString(schema)));
                                break;
                            }
                            case "header": {
                                httpRequest.withHeader(name, schemaString(OBJECT_WRITER.writeValueAsString(schema)));
                                break;
                            }
                            case "cookie": {
                                httpRequest.withCookie(name, schemaString(OBJECT_WRITER.writeValueAsString(schema)));
                                break;
                            }
                            default:
                                mockServerLogger.logEvent(
                                    new LogEntry()
                                        .setLogLevel(ERROR)
                                        .setMessageFormat("unknown value for the parameter in property, expected \"query\", \"header\", \"path\" or \"cookie\" found \"" + parameter.getIn() + "\"")
                                );
                        }
                    } catch (IOException exception) {
                        mockServerLogger.logEvent(
                            new LogEntry()
                                .setLogLevel(ERROR)
                                .setMessageFormat("exception while creating adding parameter{}from the schema{}")
                                .setArguments(parameter, openAPIDefinition)
                                .setThrowable(exception)
                        );
                    }
                }
            }
            // set matching key matching style to ensure all values are matched against any schema (not just one)
            if (httpRequest.getPathParameters() != null) {
                httpRequest.getPathParameters().withKeyMatchStyle(KeyMatchStyle.MATCHING_KEY);
            }
            if (httpRequest.getQueryStringParameters() != null) {
                httpRequest.getQueryStringParameters().withKeyMatchStyle(KeyMatchStyle.MATCHING_KEY);
            }
            if (httpRequest.getHeaders() != null) {
                httpRequest.getHeaders().withKeyMatchStyle(KeyMatchStyle.MATCHING_KEY);
            }
        }
        // security schemes
        Map> headerRequirements = new HashMap<>();
        Map> queryStringParameterRequirements = new HashMap<>();
        Map> cookieRequirements = new HashMap<>();
        buildSecurityValues(openAPI, headerRequirements, queryStringParameterRequirements, cookieRequirements, openAPI.getSecurity());
        buildSecurityValues(openAPI, headerRequirements, queryStringParameterRequirements, cookieRequirements, methodOperationPair.getRight().getSecurity());
        if (!headerRequirements.isEmpty()) {
            if (headerRequirements.keySet().size() > 1) {
                for (Map.Entry> headerMatchEntry : headerRequirements.entrySet()) {
                    httpRequest.withHeader("?" + headerMatchEntry.getKey(), Joiner.on("|").join(headerMatchEntry.getValue()));
                }
                httpRequest.withHeader(Joiner.on("|").join(headerRequirements.keySet()), ".*");
            } else if (!queryStringParameterRequirements.isEmpty() || !cookieRequirements.isEmpty()) {
                httpRequest.withHeader("?" + Joiner.on("|").join(headerRequirements.keySet()), Joiner.on("|").join(headerRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            } else {
                httpRequest.withHeader(Joiner.on("|").join(headerRequirements.keySet()), Joiner.on("|").join(headerRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            }
        }
        if (!queryStringParameterRequirements.isEmpty()) {
            if (queryStringParameterRequirements.keySet().size() > 1) {
                for (Map.Entry> queryStringParameterMatchEntry : queryStringParameterRequirements.entrySet()) {
                    httpRequest.withQueryStringParameter("?" + queryStringParameterMatchEntry.getKey(), Joiner.on("|").join(queryStringParameterMatchEntry.getValue()));
                }
                httpRequest.withQueryStringParameter(Joiner.on("|").join(queryStringParameterRequirements.keySet()), ".*");
            } else if (!headerRequirements.isEmpty() || !cookieRequirements.isEmpty()) {
                httpRequest.withQueryStringParameter("?" + Joiner.on("|").join(queryStringParameterRequirements.keySet()), Joiner.on("|").join(queryStringParameterRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            } else {
                httpRequest.withQueryStringParameter(Joiner.on("|").join(queryStringParameterRequirements.keySet()), Joiner.on("|").join(queryStringParameterRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            }
        }
        if (!cookieRequirements.isEmpty()) {
            if (cookieRequirements.keySet().size() > 1) {
                for (Map.Entry> cookieMatchEntry : cookieRequirements.entrySet()) {
                    httpRequest.withCookie("?" + cookieMatchEntry.getKey(), Joiner.on("|").join(cookieMatchEntry.getValue()));
                }
                httpRequest.withCookie(Joiner.on("|").join(cookieRequirements.keySet()), ".*");
            } else if (!queryStringParameterRequirements.isEmpty() || !headerRequirements.isEmpty()) {
                httpRequest.withCookie("?" + Joiner.on("|").join(cookieRequirements.keySet()), Joiner.on("|").join(cookieRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            } else {
                httpRequest.withCookie(Joiner.on("|").join(cookieRequirements.keySet()), Joiner.on("|").join(cookieRequirements.values().stream().flatMap(Collection::stream).collect(Collectors.toList())));
            }
        }
        return httpRequest;
    }

    private ParameterStyle parameterStyle(Boolean explode, Parameter.StyleEnum style) {
        ParameterStyle result = null;
        switch (style) {
            case MATRIX:
                if (explode) {
                    result = MATRIX_EXPLODED;
                } else {
                    result = MATRIX;
                }
                break;
            case LABEL:
                if (explode) {
                    result = LABEL_EXPLODED;
                } else {
                    result = LABEL;
                }
                break;
            case FORM:
                if (explode) {
                    result = FORM_EXPLODED;
                } else {
                    result = FORM;
                }
                break;
            case SIMPLE:
                if (explode) {
                    result = SIMPLE_EXPLODED;
                } else {
                    result = SIMPLE;
                }
                break;
            case SPACEDELIMITED:
                if (explode) {
                    result = SPACE_DELIMITED_EXPLODED;
                } else {
                    result = SPACE_DELIMITED;
                }
                break;
            case PIPEDELIMITED:
                if (explode) {
                    result = PIPE_DELIMITED_EXPLODED;
                } else {
                    result = PIPE_DELIMITED;
                }
                break;
            case DEEPOBJECT:
                result = DEEP_OBJECT;
                break;
        }
        return result;
    }

    private ParameterStyle parameterStyle(Boolean explode, Encoding.StyleEnum style) {
        ParameterStyle result = null;
        switch (style) {
            case FORM:
                if (explode) {
                    result = FORM_EXPLODED;
                } else {
                    result = FORM;
                }
                break;
            case SPACE_DELIMITED:
                if (explode) {
                    result = SPACE_DELIMITED_EXPLODED;
                } else {
                    result = SPACE_DELIMITED;
                }
                break;
            case PIPE_DELIMITED:
                if (explode) {
                    result = PIPE_DELIMITED_EXPLODED;
                } else {
                    result = PIPE_DELIMITED;
                }
                break;
            case DEEP_OBJECT:
                result = DEEP_OBJECT;
                break;
        }
        return result;
    }

    private void buildSecurityValues(OpenAPI openAPI, Map> headerRequirements, Map> queryStringParameterRequirements, Map> cookieRequirements, List security) {
        if (security != null) {
            for (SecurityRequirement securityRequirement : security) {
                if (securityRequirement != null) {
                    for (String securityRequirementName : securityRequirement.keySet()) {
                        if (openAPI.getComponents() != null && openAPI.getComponents().getSecuritySchemes() != null && openAPI.getComponents().getSecuritySchemes().get(securityRequirementName) != null) {
                            SecurityScheme securityScheme = openAPI.getComponents().getSecuritySchemes().get(securityRequirementName);
                            String scheme = securityScheme.getScheme();
                            switch (securityScheme.getType()) {
                                case APIKEY: {
                                    String parameterName = securityScheme.getName();
                                    if (isNotBlank(parameterName)) {
                                        switch (securityScheme.getIn() != null ? securityScheme.getIn() : SecurityScheme.In.HEADER) {
                                            case COOKIE: {
                                                Set cookieValues = cookieRequirements.get(parameterName);
                                                String cookieValue = ".+";
                                                if (cookieValues != null) {
                                                    cookieValues.add(cookieValue);
                                                } else {
                                                    cookieRequirements.put(parameterName, new HashSet<>(Collections.singletonList(cookieValue)));
                                                }
                                                break;
                                            }
                                            case QUERY: {
                                                Set queryStringParameterValues = queryStringParameterRequirements.get(parameterName);
                                                String queryStringParameterValue = ".+";
                                                if (queryStringParameterValues != null) {
                                                    queryStringParameterValues.add(queryStringParameterValue);
                                                } else {
                                                    queryStringParameterRequirements.put(parameterName, new HashSet<>(Collections.singletonList(queryStringParameterValue)));
                                                }
                                                break;
                                            }
                                            default:
                                            case HEADER: {
                                                Set headerValues = headerRequirements.get(parameterName);
                                                String headerValue = ".+";
                                                if (headerValues != null) {
                                                    headerValues.add(headerValue);
                                                } else {
                                                    headerRequirements.put(parameterName, new HashSet<>(Collections.singletonList(headerValue)));
                                                }
                                                break;
                                            }
                                        }
                                    }
                                    break;
                                }
                                case HTTP:
                                case OAUTH2:
                                case OPENIDCONNECT: {
                                    String parameterName = AUTHORIZATION.toString();
                                    switch (securityScheme.getIn() != null ? securityScheme.getIn() : SecurityScheme.In.HEADER) {
                                        case COOKIE: {
                                            Set cookieValues = cookieRequirements.get(parameterName);
                                            String cookieValue = (isNotBlank(scheme) ? scheme : "") + ".+";
                                            if (cookieValues != null) {
                                                cookieValues.add(cookieValue);
                                            } else {
                                                cookieRequirements.put(parameterName, new HashSet<>(Collections.singletonList(cookieValue)));
                                            }
                                            break;
                                        }
                                        case QUERY: {
                                            Set queryStringParameterValues = queryStringParameterRequirements.get(parameterName);
                                            String queryStringParameterValue = (isNotBlank(scheme) ? scheme : "") + ".+";
                                            if (queryStringParameterValues != null) {
                                                queryStringParameterValues.add(queryStringParameterValue);
                                            } else {
                                                queryStringParameterRequirements.put(parameterName, new HashSet<>(Collections.singletonList(queryStringParameterValue)));
                                            }
                                            break;
                                        }
                                        default:
                                        case HEADER: {
                                            Set headerValues = headerRequirements.get(parameterName);
                                            String headerValue = (isNotBlank(scheme) ? scheme : "") + ".+";
                                            if (headerValues != null) {
                                                headerValues.add(headerValue);
                                            } else {
                                                headerRequirements.put(parameterName, new HashSet<>(Collections.singletonList(headerValue)));
                                            }
                                            break;
                                        }
                                    }
                                    break;
                                }
                            }

                        }
                    }
                }
            }
        }
    }

    private BiConsumer handleRequestBody(OpenAPIDefinition openAPIDefinition, OpenAPI openAPI, String path, Pair methodOperationPair, Boolean required) {
        return (contentType, mediaType) -> {
            HttpRequest httpRequest = createHttpRequest(openAPIDefinition, openAPI, path, methodOperationPair);
            if (contentType.equals("multipart/form-data")) {
                throw new IllegalArgumentException("multipart form data is not supported on requestBody, found on operation: \"" + methodOperationPair.getRight().getOperationId() + "\" method: \"" + methodOperationPair.getLeft() + "\"");
            }
            if (!contentType.equals("*/*") && required) {
                // ensure that parameters added to the content type such as charset don't break the matching
                httpRequest.withHeader(CONTENT_TYPE.toString(), contentType.replaceAll("\\*", ".*") + ".*");
            }
            if (mediaType != null && mediaType.getSchema() != null) {
                Map parameterStyle = null;
                if (mediaType.getEncoding() != null) {
                    parameterStyle = new HashMap<>();
                    for (Map.Entry encodingEntry : mediaType.getEncoding().entrySet()) {
                        parameterStyle.put(encodingEntry.getKey(), parameterStyle(encodingEntry.getValue().getExplode(), encodingEntry.getValue().getStyle()));
                    }
                }
                try {
                    httpRequest.withBody(jsonSchema(OBJECT_WRITER.writeValueAsString(mediaType.getSchema())).withParameterStyles(parameterStyle).withOptional(!required));
                } catch (Throwable throwable) {
                    mockServerLogger.logEvent(
                        new LogEntry()
                            .setLogLevel(ERROR)
                            .setMessageFormat("exception while creating adding request body{}from the schema{}")
                            .setArguments(mediaType.getSchema(), openAPIDefinition)
                            .setThrowable(throwable)
                    );
                }
            }
            addRequestMatcher(openAPIDefinition, methodOperationPair, httpRequest, contentType);
        };
    }

    private void addRequestMatcher(OpenAPIDefinition openAPIDefinition, Pair methodOperationPair, HttpRequest httpRequest, String contentType) {
        httpRequests.add(httpRequest);
        HttpRequestPropertiesMatcher httpRequestPropertiesMatcher = new HttpRequestPropertiesMatcher(mockServerLogger);
        httpRequestPropertiesMatcher.update(httpRequest);
        httpRequestPropertiesMatcher.setControlPlaneMatcher(controlPlaneMatcher);
        int maxUrlOrPathLength = 40;
        int urlOrPathLength = openAPIDefinition.getSpecUrlOrPayload().length();
        String urlOrPath = (urlOrPathLength > maxUrlOrPathLength ? "..." : "") + openAPIDefinition.getSpecUrlOrPayload().substring(urlOrPathLength > maxUrlOrPathLength ? urlOrPathLength - maxUrlOrPathLength : urlOrPathLength);
        httpRequestPropertiesMatcher.setDescription("" +
            " open api" +
            (openAPIDefinition.getSpecUrlOrPayload().endsWith(".json") || openAPIDefinition.getSpecUrlOrPayload().endsWith(".yaml") ? " \"" + urlOrPath + "\"" : "") +
            (isNotBlank(methodOperationPair.getValue().getOperationId()) ? " operation \"" + methodOperationPair.getValue().getOperationId() + "\"" : "") +
            (isNotBlank(contentType) ? " content-type \"" + contentType + "\"" : "")
        );
        httpRequestPropertiesMatchers.add(httpRequestPropertiesMatcher);
    }

    @Override
    public boolean matches(MatchDifference matchDifference, RequestDefinition requestDefinition) {
        boolean result = false;
        String logCorrelationId = matchDifference != null ? matchDifference.getLogCorrelationId() : UUID.randomUUID().toString();
        if (httpRequestPropertiesMatchers != null && !httpRequestPropertiesMatchers.isEmpty()) {
            for (HttpRequestPropertiesMatcher httpRequestPropertiesMatcher : httpRequestPropertiesMatchers) {
                if (matchDifference == null) {
                    if (MockServerLogger.isEnabled(Level.TRACE) && requestDefinition instanceof HttpRequest) {
                        matchDifference = new MatchDifference(requestDefinition);
                    }
                    result = httpRequestPropertiesMatcher.matches(matchDifference, requestDefinition);
                } else {
                    MatchDifference singleMatchDifference = new MatchDifference(matchDifference.getHttpRequest());
                    result = httpRequestPropertiesMatcher.matches(singleMatchDifference, requestDefinition);
                    matchDifference.addDifferences(singleMatchDifference.getAllDifferences());
                }
                if (result) {
                    break;
                }
            }
        } else {
            result = true;
        }
        return result;
    }

    @Override
    public String toString() {
        try {
            return TO_STRING_OBJECT_WRITER
                .writeValueAsString(openAPIDefinition);
        } catch (Exception e) {
            return super.toString();
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (hashCode() != o.hashCode()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        HttpRequestsPropertiesMatcher that = (HttpRequestsPropertiesMatcher) o;
        return Objects.equals(openAPIDefinition, that.openAPIDefinition);
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            hashCode = Objects.hash(super.hashCode(), openAPIDefinition);
        }
        return hashCode;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy