Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow common errors to be bound to a service #919

Merged
merged 4 commits into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/source/1.0/spec/core/json-ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ shapes defined in JSON support the same properties as the Smithy IDL.
- [:ref:`AST shape reference <ast-shape-reference>`]
- Binds a list of resources to the service. Each reference MUST target
a resource.
* - errors
- [:ref:`AST shape reference <ast-shape-reference>`]
- Defines a list of common errors that every operation bound within the
closure of the service can return. Each provided shape ID MUST target
a :ref:`structure <structure>` shape that is marked with the
:ref:`error-trait`.
* - traits
- map of :ref:`shape ID <shape-id>` to trait values
- Traits to apply to the service
Expand All @@ -425,6 +431,11 @@ shapes defined in JSON support the same properties as the Smithy IDL.
"target": "smithy.example#SomeResource"
}
],
"errors": [
{
"target": "smithy.example#SomeError"
}
],
"traits": {
"smithy.api#documentation": "Documentation for the service"
},
Expand Down
47 changes: 47 additions & 0 deletions docs/source/1.0/spec/core/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,12 @@ The service shape supports the following properties:
- Binds a set of ``resource`` shapes to the service. Each element in
the given list MUST be a valid :ref:`shape ID <shape-id>` that targets
a :ref:`resource <resource>` shape.
* - errors
- [``string``]
- Defines a list of common errors that every operation bound within the
closure of the service can return. Each provided shape ID MUST target
a :ref:`structure <structure>` shape that is marked with the
:ref:`error-trait`.
* - rename
- map of :ref:`shape ID <shape-id>` to ``string``
- Disambiguates shape name conflicts in the
Expand Down Expand Up @@ -1060,6 +1066,47 @@ The following example defines a service with no operations or resources.
}
}

The following example defines a service shape that defines a set of errors
that are common to every operation in the service:

.. tabs::

.. code-tab:: smithy

namespace smithy.example

service MyService {
version: "2017-02-11",
errors: [SomeError]
}

@error("client")
structure SomeError {}

.. code-tab:: json

{
"smithy": "1.0",
"shapes": {
"smithy.example#MyService": {
"type": "service",
"version": "2017-02-11",
"errors": [
{
"target": "smithy.example#SomeError"
}
]
},
"smithy.example#SomeError": {
"type": "structure",
"traits": {
"smithy.api#error": "client"
}
}
}
}



.. _service-operations:

Expand Down
3 changes: 3 additions & 0 deletions docs/source/1.0/spec/core/selectors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,9 @@ The table below lists the labeled directed relationships from each shape.
* - service
- resource
- Each resource that is bound to a service.
* - service
- error
- Each error structure referenced by the service (if present).
* - resource
- identifier
- The identifier referenced by the resource (if specified).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import software.amazon.smithy.diff.ChangedShape;
import software.amazon.smithy.diff.Differences;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.validation.ValidationEvent;

/**
* Emits a warning when an error is added to a service.
*/
public final class AddedServiceError extends AbstractDiffEvaluator {
@Override
public List<ValidationEvent> evaluate(Differences differences) {
return differences.changedShapes(ServiceShape.class)
.flatMap(change -> createErrorViolations(change).stream())
.collect(Collectors.toList());
}

private List<ValidationEvent> createErrorViolations(ChangedShape<ServiceShape> change) {
if (change.getOldShape().getErrors().equals(change.getNewShape().getErrors())) {
return Collections.emptyList();
}

List<ValidationEvent> events = new ArrayList<>();
for (ShapeId id : change.getNewShape().getErrors()) {
if (!change.getOldShape().getErrors().contains(id)) {
events.add(warning(change.getNewShape(), String.format(
mtdowling marked this conversation as resolved.
Show resolved Hide resolved
"The `%s` error was added to the `%s` service, making this error common "
+ "to all operations within the service. This is backward-compatible if the "
+ "error is only encountered as a result of a change in behavior of "
+ "the client (for example, the client sends a new "
+ "parameter to an operation).",
id, change.getShapeId())));
}
}

return events;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import software.amazon.smithy.diff.ChangedShape;
import software.amazon.smithy.diff.Differences;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.validation.ValidationEvent;

/**
* Emits a warning when an error is removed from a service.
*/
public final class RemovedServiceError extends AbstractDiffEvaluator {
@Override
public List<ValidationEvent> evaluate(Differences differences) {
return differences.changedShapes(ServiceShape.class)
.flatMap(change -> createErrorViolations(change).stream())
.collect(Collectors.toList());
}

private List<ValidationEvent> createErrorViolations(ChangedShape<ServiceShape> change) {
if (change.getOldShape().getErrors().equals(change.getNewShape().getErrors())) {
return Collections.emptyList();
}

List<ValidationEvent> events = new ArrayList<>();
for (ShapeId id : change.getOldShape().getErrors()) {
if (!change.getNewShape().getErrors().contains(id)) {
events.add(warning(change.getNewShape(), String.format(
"The `%s` error was removed from the `%s` service. This means that it "
+ "is no longer considered an error common to all operations within the "
+ "service.",
change.getShapeId(), id)));
}
}

return events;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ software.amazon.smithy.diff.evaluators.AddedEntityBinding
software.amazon.smithy.diff.evaluators.AddedMetadata
software.amazon.smithy.diff.evaluators.AddedOperationError
software.amazon.smithy.diff.evaluators.AddedOperationInputOutput
software.amazon.smithy.diff.evaluators.AddedServiceError
software.amazon.smithy.diff.evaluators.AddedShape
software.amazon.smithy.diff.evaluators.AddedTraitDefinition
software.amazon.smithy.diff.evaluators.ChangedEnumTrait
Expand All @@ -21,6 +22,7 @@ software.amazon.smithy.diff.evaluators.RemovedMetadata
software.amazon.smithy.diff.evaluators.RemovedOperationError
software.amazon.smithy.diff.evaluators.RemovedOperationInput
software.amazon.smithy.diff.evaluators.RemovedOperationOutput
software.amazon.smithy.diff.evaluators.RemovedServiceError
software.amazon.smithy.diff.evaluators.RemovedShape
software.amazon.smithy.diff.evaluators.RemovedTraitDefinition
software.amazon.smithy.diff.evaluators.ServiceRename
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 java.util.List;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.diff.ModelDiff;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.model.validation.ValidationEvent;

public class AddedServiceErrorTest {
@Test
public void detectsAddedErrors() {
Shape e1 = StructureShape.builder()
.id("foo.baz#E1")
.addTrait(new ErrorTrait("client"))
.build();
Shape e2 = StructureShape.builder()
.id("foo.baz#E2")
.addTrait(new ErrorTrait("client"))
.build();
ServiceShape service1 = ServiceShape.builder().id("foo.baz#S").version("X").build();
ServiceShape service2 = service1.toBuilder().addError(e1.getId()).addError(e2.getId()).build();
Model modelA = Model.assembler().addShapes(service1, e1, e2).assemble().unwrap();
Model modelB = Model.assembler().addShapes(service2, e1, e2).assemble().unwrap();
List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);

assertThat(TestHelper.findEvents(events, "AddedServiceError").size(), equalTo(2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,17 @@ public void detectsRemovedErrors() {
.id("foo.baz#E2")
.addTrait(new ErrorTrait("client"))
.build();
OperationShape operation1 = OperationShape.builder().id("foo.baz#Operation").build();
Shape operation2 = operation1.toBuilder().addError(e1.getId()).addError(e2.getId()).build();
Model modelA = Model.assembler().addShapes(operation2, e1, e2).assemble().unwrap();
Model modelB = Model.assembler().addShapes(operation1, e1, e2).assemble().unwrap();
OperationShape operation1 = OperationShape.builder()
.id("foo.baz#Operation")
.addError(e1)
.addError(e2)
.build();
Shape operation2 = operation1.toBuilder().clearErrors().build();
Model modelA = Model.assembler().addShapes(operation1, e1, e2).assemble().unwrap();
Model modelB = Model.assembler().addShapes(operation2, e1, e2).assemble().unwrap();
List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);

// Emits an event for each removal.
assertThat(TestHelper.findEvents(events, "RemovedOperationError").size(), equalTo(2));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 java.util.List;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.diff.ModelDiff;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.model.validation.ValidationEvent;

public class RemovedServiceErrorTest {
@Test
public void detectsRemovedErrors() {
Shape e1 = StructureShape.builder()
.id("foo.baz#E1")
.addTrait(new ErrorTrait("client"))
.build();
Shape e2 = StructureShape.builder()
.id("foo.baz#E2")
.addTrait(new ErrorTrait("client"))
.build();
ServiceShape service1 = ServiceShape.builder()
.id("foo.baz#S")
.version("X")
.addError(e1)
.addError(e2)
.build();
ServiceShape service2 = service1.toBuilder().clearErrors().build();
Model modelA = Model.assembler().addShapes(service1, e1, e2).assemble().unwrap();
Model modelB = Model.assembler().addShapes(service2, e1, e2).assemble().unwrap();
List<ValidationEvent> events = ModelDiff.compare(modelA, modelB);

// Emits one even for both removals.
assertThat(TestHelper.findEvents(events, "RemovedServiceError").size(), equalTo(2));
}
}
Loading