Add @required and @default smithy-diff support
You can only add the @default trait to replace the @required trait. You can only remove the @required trait if it's replaced by the @default trait or if the containing structure has the @input trait.
This commit is contained in:
parent
f8968b9462
commit
4096eda0ec
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2021 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.evaluators;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import software.amazon.smithy.diff.Differences;
|
||||
import software.amazon.smithy.model.shapes.MemberShape;
|
||||
import software.amazon.smithy.model.traits.DefaultTrait;
|
||||
import software.amazon.smithy.model.traits.RequiredTrait;
|
||||
import software.amazon.smithy.model.validation.ValidationEvent;
|
||||
|
||||
/**
|
||||
* The default trait can only be added to shape if it's replacing the
|
||||
* required trait.
|
||||
*/
|
||||
public class AddedDefaultTrait extends AbstractDiffEvaluator {
|
||||
@Override
|
||||
public List<ValidationEvent> evaluate(Differences differences) {
|
||||
return differences.changedShapes(MemberShape.class)
|
||||
.map(change -> {
|
||||
MemberShape oldShape = change.getOldShape();
|
||||
MemberShape newShape = change.getNewShape();
|
||||
if (newShape.hasTrait(DefaultTrait.class)
|
||||
&& !oldShape.hasTrait(DefaultTrait.class)
|
||||
&& !oldShape.hasTrait(RequiredTrait.class)) {
|
||||
return error(newShape, "Added the @default trait. This is only backward compatible if "
|
||||
+ "the @default trait is used to replace the @required trait.");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2021 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.evaluators;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import software.amazon.smithy.diff.Differences;
|
||||
import software.amazon.smithy.model.Model;
|
||||
import software.amazon.smithy.model.shapes.MemberShape;
|
||||
import software.amazon.smithy.model.traits.DefaultTrait;
|
||||
import software.amazon.smithy.model.traits.InputTrait;
|
||||
import software.amazon.smithy.model.traits.RequiredTrait;
|
||||
import software.amazon.smithy.model.validation.ValidationEvent;
|
||||
|
||||
/**
|
||||
* When removing the required trait, it has to be replaced with the default
|
||||
* trait, unless the containing structure is marked with the input trait.
|
||||
*/
|
||||
public class RemovedRequiredTrait extends AbstractDiffEvaluator {
|
||||
@Override
|
||||
public List<ValidationEvent> evaluate(Differences differences) {
|
||||
return differences.changedShapes(MemberShape.class)
|
||||
.map(change -> {
|
||||
MemberShape oldShape = change.getOldShape();
|
||||
MemberShape newShape = change.getNewShape();
|
||||
if (oldShape.hasTrait(RequiredTrait.class)
|
||||
&& !newShape.hasTrait(RequiredTrait.class)
|
||||
&& !newShape.hasTrait(DefaultTrait.class)
|
||||
&& !containerHasInputTrait(differences.getNewModel(), newShape)) {
|
||||
return error(newShape, "Removed the @required trait without replacing it with the @default "
|
||||
+ "trait. Code generated for this structure will change in a backward "
|
||||
+ "incompatible way in many languages, including Rust, Kotlin, Swift, "
|
||||
+ "and many others.");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean containerHasInputTrait(Model model, MemberShape member) {
|
||||
return model.getShape(member.getContainer())
|
||||
.filter(container -> container.hasTrait(InputTrait.class))
|
||||
.isPresent();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
software.amazon.smithy.diff.evaluators.AddedDefaultTrait
|
||||
software.amazon.smithy.diff.evaluators.AddedEntityBinding
|
||||
software.amazon.smithy.diff.evaluators.AddedMetadata
|
||||
software.amazon.smithy.diff.evaluators.AddedOperationError
|
||||
|
@ -19,6 +20,7 @@ software.amazon.smithy.diff.evaluators.RemovedAuthenticationScheme
|
|||
software.amazon.smithy.diff.evaluators.RemovedEntityBinding
|
||||
software.amazon.smithy.diff.evaluators.RemovedMetadata
|
||||
software.amazon.smithy.diff.evaluators.RemovedOperationError
|
||||
software.amazon.smithy.diff.evaluators.RemovedRequiredTrait
|
||||
software.amazon.smithy.diff.evaluators.RemovedServiceError
|
||||
software.amazon.smithy.diff.evaluators.RemovedShape
|
||||
software.amazon.smithy.diff.evaluators.RemovedTraitDefinition
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2021 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.evaluators;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import software.amazon.smithy.diff.ModelDiff;
|
||||
import software.amazon.smithy.model.Model;
|
||||
import software.amazon.smithy.model.shapes.StringShape;
|
||||
import software.amazon.smithy.model.shapes.StructureShape;
|
||||
import software.amazon.smithy.model.traits.DefaultTrait;
|
||||
import software.amazon.smithy.model.traits.RequiredTrait;
|
||||
import software.amazon.smithy.model.validation.Severity;
|
||||
|
||||
public class AddedDefaultTraitTest {
|
||||
@Test
|
||||
public void replacingRequiredTraitWithDefaultIsOk() {
|
||||
StringShape s = StringShape.builder().id("smithy.example#Str").build();
|
||||
StructureShape a = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b1 -> b1.addTrait(new RequiredTrait()))
|
||||
.build();
|
||||
StructureShape b = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b2 -> b2.addTrait(new DefaultTrait()))
|
||||
.build();
|
||||
Model model1 = Model.builder().addShapes(s, a).build();
|
||||
Model model2 = Model.builder().addShapes(s, b).build();
|
||||
ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
|
||||
|
||||
assertThat(result.getDiffEvents().stream()
|
||||
.filter(event -> event.getId().equals("AddedDefaultTrait"))
|
||||
.count(), equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsInvalidAdditionOfDefaultTrait() {
|
||||
StringShape s = StringShape.builder().id("smithy.example#Str").build();
|
||||
StructureShape a = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId())
|
||||
.build();
|
||||
StructureShape b = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), builder -> builder.addTrait(new DefaultTrait()))
|
||||
.build();
|
||||
Model model1 = Model.builder().addShapes(s, a).build();
|
||||
Model model2 = Model.builder().addShapes(s, b).build();
|
||||
ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
|
||||
|
||||
assertThat(result.isDiffBreaking(), is(true));
|
||||
assertThat(result.getDiffEvents().stream()
|
||||
.filter(event -> event.getSeverity() == Severity.ERROR)
|
||||
.filter(event -> event.getId().equals("AddedDefaultTrait"))
|
||||
.filter(event -> event.getShapeId().get().equals(a.getAllMembers().get("foo").getId()))
|
||||
.filter(event -> event.getMessage().contains("Added the @default trait"))
|
||||
.count(), equalTo(1L));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2021 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.evaluators;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import software.amazon.smithy.diff.ModelDiff;
|
||||
import software.amazon.smithy.model.Model;
|
||||
import software.amazon.smithy.model.shapes.StringShape;
|
||||
import software.amazon.smithy.model.shapes.StructureShape;
|
||||
import software.amazon.smithy.model.traits.DefaultTrait;
|
||||
import software.amazon.smithy.model.traits.InputTrait;
|
||||
import software.amazon.smithy.model.traits.RequiredTrait;
|
||||
import software.amazon.smithy.model.validation.Severity;
|
||||
|
||||
public class RemovedRequiredTraitTest {
|
||||
@Test
|
||||
public void replacingRequiredTraitWithDefaultIsOk() {
|
||||
StringShape s = StringShape.builder().id("smithy.example#Str").build();
|
||||
StructureShape a = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b1 -> b1.addTrait(new RequiredTrait()))
|
||||
.build();
|
||||
StructureShape b = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b2 -> b2.addTrait(new DefaultTrait()))
|
||||
.build();
|
||||
Model model1 = Model.builder().addShapes(s, a).build();
|
||||
Model model2 = Model.builder().addShapes(s, b).build();
|
||||
ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
|
||||
|
||||
assertThat(result.getDiffEvents().stream()
|
||||
.filter(event -> event.getId().equals("RemovedRequiredTrait"))
|
||||
.count(), equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removingTheRequiredTraitOnInputStructureIsOk() {
|
||||
StringShape s = StringShape.builder().id("smithy.example#Str").build();
|
||||
StructureShape a = StructureShape.builder()
|
||||
.addTrait(new InputTrait())
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b1 -> b1.addTrait(new RequiredTrait()))
|
||||
.build();
|
||||
StructureShape b = StructureShape.builder()
|
||||
.addTrait(new InputTrait())
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId())
|
||||
.build();
|
||||
Model model1 = Model.builder().addShapes(s, a).build();
|
||||
Model model2 = Model.builder().addShapes(s, b).build();
|
||||
ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
|
||||
|
||||
assertThat(result.getDiffEvents().stream()
|
||||
.filter(event -> event.getId().equals("RemovedRequiredTrait"))
|
||||
.count(), equalTo(0L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsInvalidRemovalOfRequired() {
|
||||
StringShape s = StringShape.builder().id("smithy.example#Str").build();
|
||||
StructureShape a = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId(), b1 -> b1.addTrait(new RequiredTrait()))
|
||||
.build();
|
||||
StructureShape b = StructureShape.builder()
|
||||
.id("smithy.example#A")
|
||||
.addMember("foo", s.getId())
|
||||
.build();
|
||||
Model model1 = Model.builder().addShapes(s, a).build();
|
||||
Model model2 = Model.builder().addShapes(s, b).build();
|
||||
ModelDiff.Result result = ModelDiff.builder().oldModel(model1).newModel(model2).compare();
|
||||
|
||||
assertThat(result.isDiffBreaking(), is(true));
|
||||
assertThat(result.getDiffEvents().stream()
|
||||
.filter(event -> event.getSeverity() == Severity.ERROR)
|
||||
.filter(event -> event.getId().equals("RemovedRequiredTrait"))
|
||||
.filter(event -> event.getShapeId().get().equals(a.getAllMembers().get("foo").getId()))
|
||||
.filter(event -> event.getMessage().contains("Removed the @required trait"))
|
||||
.count(), equalTo(1L));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue