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

io.kemtoa.swagger.compat.walker.SwaggerDiffWalker Maven / Gradle / Ivy

package io.kemtoa.swagger.compat.walker;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import io.swagger.models.ArrayModel;
import io.swagger.models.HttpMethod;
import io.swagger.models.Model;
import io.swagger.models.ModelImpl;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.RefModel;
import io.swagger.models.Response;
import io.swagger.models.Swagger;
import io.swagger.models.parameters.BodyParameter;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.ObjectProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;

/**
 * Swagger API tool for comparing two specification documents.
 *
 * Traverses a pair of Swagger 2.0 json documents, calling the specified instance
 * of {@link SwaggerDiffVisitor} for each encountered node present in at least
 * one of the documents.
 *
 * Keeps track of the position in the tree using {@link Location}.
 */
public class SwaggerDiffWalker {

    private Swagger swaggerLeft;
    private Swagger swaggerRight;
    private Location location = new Location();

    private Set visitedModels = new HashSet<>(); // Used to prevent infinite recursion

    public void walk(SwaggerDiffVisitor visitor, Swagger swaggerLeft, Swagger swaggerRight) {
        this.swaggerLeft = swaggerLeft;
        this.swaggerRight = swaggerRight;

        visitor.setLocation(location);

        Set leftKeys  = swaggerLeft.getPaths() != null ? swaggerLeft.getPaths().keySet() : new HashSet<>();
        Set rightKeys = swaggerRight.getPaths() != null ? swaggerRight.getPaths().keySet() : new HashSet<>();
        Stream.concat(
                leftKeys.stream(),
                rightKeys.stream()
        ).distinct().forEach(key -> doVisitAndRecurse(
                visitor, key,
                swaggerLeft.getPath(key),
                swaggerRight.getPath(key))
        );
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, String pathKey, Path left, Path right) {
        location.pushPath("Path " + pathKey);

        try {
            visitor.acceptPath(pathKey, left, right);

            if (left == null || right == null) {
                return;
            }

            Set leftKeys = left.getOperationMap().keySet();
            Set rightKeys = right.getOperationMap().keySet();
            Stream.concat(
                    leftKeys.stream(),
                    rightKeys.stream()
            ).distinct().forEach(key -> doVisitAndRecurse(visitor, key,
                    left.getOperationMap().get(key),
                    right.getOperationMap().get(key))
            );
        } finally {
            location.popPath();
        }
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, HttpMethod operationKey, Operation left, Operation right) {
        location.pushPath("Operation " + operationKey);

        try {
            visitor.acceptOperation(operationKey, left, right);

            if (left == null || right == null) {
                return;
            }

            Stream.concat(
                    left.getParameters().stream().map(Parameter::getName),
                    right.getParameters().stream().map(Parameter::getName)
            ).distinct().forEach(name -> {
                Parameter leftParam = left.getParameters().stream().filter(param -> param.getName().equals(name)).findAny().orElse(null);
                Parameter rightParam = right.getParameters().stream().filter(param -> param.getName().equals(name)).findAny().orElse(null);
                doVisitAndRecurse(visitor, leftParam, rightParam);
            });

            Set leftKeys  = left.getResponses() != null ? left.getResponses().keySet() : new HashSet<>();
            Set rightKeys = right.getResponses() != null ? right.getResponses().keySet() : new HashSet<>();
            Stream.concat(
                    leftKeys.stream(),
                    rightKeys.stream()
            ).distinct().forEach(key -> doVisitAndRecurse(visitor, key,
                    left.getResponses().get(key),
                    right.getResponses().get(key))
            );
        } finally {
            location.popPath();
        }
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, Parameter left, Parameter right) {
        location.pushPath("Parameter " + (left != null ? left.getName() : right.getName()));
        location.setRequest(true);

        try {
            visitor.acceptParameter(left, right);

            if (left == null || right == null) {
                return;
            }

            if (left instanceof BodyParameter || right instanceof BodyParameter) {
                doVisitAndRecurse(visitor,
                        Optional.of(left).map(BodyParameter.class::cast).map(BodyParameter::getSchema).orElse(null),
                        Optional.of(right).map(BodyParameter.class::cast).map(BodyParameter::getSchema).orElse(null)
                );
            }
        } finally {
            visitedModels.clear();
            location.setRequest(false);
            location.popPath();
        }
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, String key, Response left, Response right) {
        location.pushPath("Response " + key);
        location.setResponse(true);

        try {
            visitor.acceptResponse(key, left, right);

            if (left == null || right == null) {
                return;
            }

            if (left.getResponseSchema() == null && right.getResponseSchema() == null) {
                return;
            }

            doVisitAndRecurse(visitor, left.getResponseSchema(), right.getResponseSchema());
        } finally {
            visitedModels.clear();
            location.setResponse(false);
            location.popPath();
        }
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, Model left, Model right) {
        if (visitedModels.contains(left) && visitedModels.contains(right)) {
            // Prevent infinite recursion
            return;
        }

        visitor.acceptModel(left, right);

        if (left != null) {
            visitedModels.add(left);
        }
        if (right != null) {
            visitedModels.add(right);
        }

        if (left == null || right == null) {
            return;
        }

        if (left instanceof ArrayModel && right instanceof ArrayModel) {
            doVisitAndRecurse(visitor, "items", ((ArrayModel) left).getItems(), ((ArrayModel) right).getItems());
        } else if (left instanceof ModelImpl && right instanceof ModelImpl) {
            doVisitEnumValues(visitor, ((ModelImpl) left).getEnum(), ((ModelImpl) right).getEnum());
            doVisitAndRecurse(visitor, left.getProperties(), right.getProperties());
        } else if (left instanceof RefModel && right instanceof RefModel) {
            RefModel leftRef  = (RefModel) left;
            RefModel rightRef = (RefModel) right;

            Model leftModel = resolveModel(swaggerLeft, leftRef.getSimpleRef());
            Model rightModel = resolveModel(swaggerRight, rightRef.getSimpleRef());

            doVisitAndRecurse(visitor, leftModel, rightModel);
        }
    }

    private Model resolveModel(Swagger swagger, String simpleRef) {
        Map definitions = swagger.getDefinitions();
        if (definitions == null) {
            return null;
        }

        return definitions.get(simpleRef);
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, Map left, Map right) {
        if (left == null || right == null) {
            return;
        }

        Set leftKeys  = left.keySet();
        Set rightKeys = right.keySet();
        Stream.concat(
                leftKeys.stream(),
                rightKeys.stream()
        ).distinct().forEach(key -> doVisitAndRecurse(visitor, key,
                left.get(key),
                right.get(key))
        );
    }

    private void doVisitAndRecurse(SwaggerDiffVisitor visitor, String name, Property left, Property right) {
        location.pushPath("Property " + name);

        try {
            visitor.acceptProperty(name, left, right);

            if (left == null || right == null) {
                return;
            }

            if (left instanceof RefProperty && right instanceof RefProperty) {
                RefProperty leftRef  = (RefProperty) left;
                RefProperty rightRef = (RefProperty) right;

                Model leftModel = resolveModel(swaggerLeft, leftRef.getSimpleRef());
                Model rightModel = resolveModel(swaggerRight, rightRef.getSimpleRef());

                doVisitAndRecurse(visitor, leftModel, rightModel);
            } else if (left instanceof ObjectProperty && right instanceof ObjectProperty) {
                ObjectProperty leftObject  = (ObjectProperty) left;
                ObjectProperty rightObject = (ObjectProperty) right;

                doVisitAndRecurse(visitor, leftObject.getProperties(), rightObject.getProperties());
            } else if (left instanceof ArrayProperty && right instanceof ArrayProperty) {
                ArrayProperty leftArray = (ArrayProperty) left;
                ArrayProperty rightArray = (ArrayProperty) right;

                doVisitAndRecurse(visitor, "items", leftArray.getItems(), rightArray.getItems());
            } else if (left instanceof StringProperty && right instanceof StringProperty) {
                StringProperty leftString = (StringProperty) left;
                StringProperty rightString = (StringProperty) right;

                doVisitEnumValues(visitor, leftString.getEnum(), rightString.getEnum());
            }
        } finally {
            location.popPath();
        }
    }

    private void doVisitEnumValues(SwaggerDiffVisitor visitor, List leftValues, List rightValues) {
        Set enumValues = new HashSet<>();
        if (leftValues != null) {
            enumValues.addAll(leftValues);
        }
        if (rightValues != null) {
            enumValues.addAll(rightValues);
        }

        for (String value : enumValues) {
            visitor.acceptEnumValue(
                    leftValues != null && leftValues.contains(value) ? value : null,
                    rightValues != null && rightValues.contains(value) ? value : null
            );
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy