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

software.amazon.smithy.diff.ModelDiff Maven / Gradle / Ivy

Go to download

This module detects differences between two Smithy models, identifying changes that are safe and changes that are backward incompatible.

The newest version!
/*
 * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package software.amazon.smithy.diff;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.ValidationEventDecorator;
import software.amazon.smithy.model.validation.suppressions.ModelBasedEventDecorator;
import software.amazon.smithy.utils.SmithyBuilder;

/**
 * Computes the difference between two models and any problems that might
 * occur due to those differences.
 */
public final class ModelDiff {

    private ModelDiff() {}

    /**
     * Creates a new ModelDiff.Builder that provides in-depth diff analysis.
     *
     * @return Returns the builder.
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Evaluates the differences between two models.
     *
     * 

Use {@link Builder} directly to get access to additional information. * * @param oldModel Previous version of the model. * @param newModel New model to compare. * @return Returns the computed validation events. */ public static List compare(Model oldModel, Model newModel) { return compare(ModelDiff.class.getClassLoader(), oldModel, newModel); } /** * Evaluates the differences between two models. * *

Use {@link Builder} directly to get access to additional information. * * @param classLoader ClassLoader used to find {@link DiffEvaluator} service providers. * @param oldModel Previous version of the model. * @param newModel New model to compare. * @return Returns the computed validation events. */ public static List compare(ClassLoader classLoader, Model oldModel, Model newModel) { return builder() .oldModel(oldModel) .newModel(newModel) .classLoader(classLoader) .compare() .getDiffEvents(); } /** * The result of comparing two Smithy models. */ public static final class Result { private final Differences differences; private final List diffEvents; private final List oldModelEvents; private final List newModelEvents; public Result( Differences differences, List diffEvents, List oldModelEvents, List newModelEvents ) { this.differences = Objects.requireNonNull(differences); this.diffEvents = Objects.requireNonNull(diffEvents); this.oldModelEvents = Objects.requireNonNull(oldModelEvents); this.newModelEvents = Objects.requireNonNull(newModelEvents); } /** * Gets a queryable set of differences between two models. * * @return Returns the differences. */ public Differences getDifferences() { return differences; } /** * Gets the diff analysis as a list of {@link ValidationEvent}s. * * @return Returns the diff validation events. */ public List getDiffEvents() { return diffEvents; } /** * Gets the validation events emitted when validating the old model. * * @return Returns the old model's validation events. */ public List getOldModelEvents() { return oldModelEvents; } /** * Gets the validation events emitted when validating the new model. * * @return Returns the new model's validation events. */ public List getNewModelEvents() { return newModelEvents; } /** * Gets the validation events that were present in the old model but * are no longer an issue in the new model. * * @return Returns the resolved validation events. */ public Set determineResolvedEvents() { Set events = new TreeSet<>(getOldModelEvents()); events.removeAll(getNewModelEvents()); return events; } /** * Gets the validation events that were introduced by whatever changes * were made to the new model. * * @return Returns the validation events introduced by the new model. */ public Set determineIntroducedEvents() { Set events = new TreeSet<>(getNewModelEvents()); events.removeAll(getOldModelEvents()); return events; } /** * Determines if the diff events contain any DANGER or ERROR events. * * @return Returns true if this diff has breaking changes. */ public boolean isDiffBreaking() { for (ValidationEvent event : getDiffEvents()) { if (event.getSeverity() == Severity.ERROR || event.getSeverity() == Severity.DANGER) { return true; } } return false; } @Override public boolean equals(Object o) { if (this == o) { return true; } else if (!(o instanceof Result)) { return false; } Result result = (Result) o; return getDifferences().equals(result.getDifferences()) && getDiffEvents().equals(result.getDiffEvents()) && getOldModelEvents().equals(result.getOldModelEvents()) && getNewModelEvents().equals(result.getNewModelEvents()); } @Override public int hashCode() { return Objects.hash(getDifferences(), getDiffEvents(), getOldModelEvents(), getNewModelEvents()); } } /** * Builder used to construct a diff of two Smithy models. */ public static final class Builder { private Model oldModel; private Model newModel; private List oldModelEvents = Collections.emptyList(); private List newModelEvents = Collections.emptyList(); private ClassLoader classLoader = ModelDiff.class.getClassLoader(); private Builder() {} /** * Sets the ClassLoader used to find {@link DiffEvaluator} service * providers. * * @param classLoader ClassLoader to use. * @return Returns the builder. */ public Builder classLoader(ClassLoader classLoader) { this.classLoader = Objects.requireNonNull(classLoader); return this; } /** * Sets the old model to compare against. * * @param oldModel Old version of a model. * @return Returns the builder. */ public Builder oldModel(Model oldModel) { this.oldModel = Objects.requireNonNull(oldModel); return this; } /** * Sets the new model to compare against. * * @param newModel New version of a model. * @return Returns the builder. */ public Builder newModel(Model newModel) { this.newModel = Objects.requireNonNull(newModel); return this; } /** * Sets the old model to compare against along with the validation * events encountered while loading the model. * * @param oldModel Old version of a model with events. * @return Returns the builder. */ public Builder oldModel(ValidatedResult oldModel) { this.oldModel = oldModel.getResult() .orElseThrow(() -> new IllegalArgumentException("No old model present in ValidatedResult")); this.oldModelEvents = oldModel.getValidationEvents(); return this; } /** * Sets the new model to compare against along with the validation * events encountered while loading the model. * * @param newModel New version of a model with events. * @return Returns the builder. */ public Builder newModel(ValidatedResult newModel) { this.newModel = newModel.getResult() .orElseThrow(() -> new IllegalArgumentException("No new model present in ValidatedResult")); this.newModelEvents = newModel.getValidationEvents(); return this; } /** * Performs the diff of the old and new models. * * @return Returns the diff {@link Result}. * @throws IllegalStateException if {@code oldModel} and {@code newModel} are not set. */ public Result compare() { SmithyBuilder.requiredState("oldModel", oldModel); SmithyBuilder.requiredState("newModel", newModel); List evaluators = new ArrayList<>(); ServiceLoader.load(DiffEvaluator.class, classLoader).forEach(evaluators::add); Differences differences = Differences.detect(oldModel, newModel); // Applies suppressions and elevates event severities. ValidationEventDecorator decoratorResult = new ModelBasedEventDecorator() .createDecorator(newModel) .getResult() .orElse(ValidationEventDecorator.IDENTITY); List diffEvents = evaluators.parallelStream() .flatMap(evaluator -> evaluator.evaluate(differences).stream()) // No need to call canDecorate first since that method will always return true in any code path. .map(decoratorResult::decorate) .collect(Collectors.toList()); return new Result(differences, diffEvents, oldModelEvents, newModelEvents); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy