Skip to content

Commit

Permalink
Allow examples for service level errors
Browse files Browse the repository at this point in the history
This commit enables validating error examples for operations with errors
that are bound to services. All services the operation with said example
are bound to must bind the error.
  • Loading branch information
kstich committed Jun 5, 2024
1 parent 96b7bee commit e725ff3
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.ExamplesTrait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.NodeValidationVisitor;
Expand Down Expand Up @@ -75,7 +79,11 @@ private List<ValidationEvent> validateExamples(Model model, OperationShape shape
} else if (isErrorDefined) {
ExamplesTrait.ErrorExample errorExample = example.getError().get();
Optional<Shape> errorShape = model.getShape(errorExample.getShapeId());
if (errorShape.isPresent() && shape.getErrors().contains(errorExample.getShapeId())) {
if (errorShape.isPresent() && (
// The error is directly bound to the operation.
shape.getErrors().contains(errorExample.getShapeId())
// The error is bound to all services that contain the operation.
|| servicesContainError(model, shape, errorExample.getShapeId()))) {
NodeValidationVisitor validator = createVisitor(
"error", errorExample.getContent(), model, shape, example);
events.addAll(errorShape.get().accept(validator));
Expand All @@ -90,6 +98,30 @@ private List<ValidationEvent> validateExamples(Model model, OperationShape shape
return events;
}

private boolean servicesContainError(Model model, OperationShape shape, ShapeId errorId) {
TopDownIndex topDownIndex = TopDownIndex.of(model);

Set<ServiceShape> services = model.getServiceShapes();
if (services.isEmpty()) {
return false;
}

for (ServiceShape service : services) {
// Skip if the service doesn't have the operation.
if (!topDownIndex.getContainedOperations(service).contains(shape)) {
continue;
}

// We've already checked if the operation contains the error,
// so a service having no errors means we've failed.
if (service.getErrors().isEmpty() || !service.getErrors().contains(errorId)) {
return false;
}
}

return true;
}

private NodeValidationVisitor createVisitor(
String name,
ObjectNode value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ERROR] ns.foo#WithError: Error parameters provided for operation without the `ns.foo#ServiceError` error: `Testing 2` | ExamplesTrait
[ERROR] ns.foo#WithoutError: Error parameters provided for operation without the `ns.foo#OperationError` error: `Testing 3` | ExamplesTrait
[ERROR] ns.foo#WithoutError: Error parameters provided for operation without the `ns.foo#ServiceError` error: `Testing 4` | ExamplesTrait
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
$version: "2"

namespace ns.foo

service ErrorBound {
version: "2020-07-02"
operations: [
WithError
WithoutError
]
errors: [ServiceError]
}

service ErrorBound2 {
version: "2020-07-02"
operations: [
WithoutError2
]
errors: [ServiceError2]
}

service NoError {
version: "2020-07-02"
operations: [
WithError
WithoutError
]
}

@examples([
{
"title": "Testing 1",
"error": {
"shapeId": "ns.foo#OperationError"
"content": {
"foo": "baz"
}
}
}
{
"title": "Testing 2"
"error": {
"shapeId": "ns.foo#ServiceError"
"content": {
"foo": "baz"
}
}
}
])
operation WithError {
input := {}
errors: [OperationError]
}

@examples([
{
"title": "Testing 3",
"error": {
"shapeId": "ns.foo#OperationError"
"content": {
"foo": "baz"
}
}
}
{
"title": "Testing 4"
"error": {
"shapeId": "ns.foo#ServiceError"
"content": {
"foo": "baz"
}
}
}
])
operation WithoutError {
input := {}
output := {}
}

@examples([
{
"title": "Testing 5"
"error": {
"shapeId": "ns.foo#ServiceError2"
"content": {
"foo": "baz"
}
}
}
])
operation WithoutError2 {
input := {}
output := {}
}

@error("client")
structure ServiceError {
foo: String
}

@error("client")
structure ServiceError2 {
foo: String
}

@error("client")
structure OperationError {
foo: String
}

0 comments on commit e725ff3

Please sign in to comment.