diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java index edd0608a531..20fe3fa0d59 100644 --- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/clientendpointdiscovery/ClientEndpointDiscoveryValidator.java @@ -42,6 +42,9 @@ @SmithyInternalApi public final class ClientEndpointDiscoveryValidator extends AbstractValidator { private static final Set VALID_INPUT_MEMBERS = SetUtils.of("Operation", "Identifiers"); + private static final String MISSING_ERROR_DEFINITION = "MissingErrorDefinition"; + private static final String UNBOUND_OPERATION = "UnboundOperation"; + private static final String NO_OPERATIONS = "NoOperations"; @Override public List validate(Model model) { @@ -73,7 +76,8 @@ public List validate(Model model) { private List validateTrait(ServiceShape service, ClientEndpointDiscoveryTrait trait) { if (!trait.getOptionalError().isPresent()) { return ListUtils.of(danger(service, trait, - "Services SHOULD define an error which indicates an endpoint is invalid.")); + "Services SHOULD define an error which indicates an endpoint is invalid.", + MISSING_ERROR_DEFINITION)); } return Collections.emptyList(); } @@ -86,8 +90,9 @@ private List validateServices( .filter(pair -> !pair.getKey().getAllOperations().contains(pair.getValue().getOperation())) .map(pair -> error(pair.getKey(), String.format( "The operation `%s` must be bound to the service `%s` to use it as the endpoint operation.", - pair.getValue().getOperation(), pair.getKey() - ))) + pair.getValue().getOperation(), pair.getKey()), + UNBOUND_OPERATION, pair.getValue().getOperation().getName() + )) .collect(Collectors.toList()); validationEvents.addAll(endpointDiscoveryServices.keySet().stream() @@ -96,8 +101,9 @@ private List validateServices( "The service `%s` is configured to use endpoint discovery, but has no operations bound with " + "the `%s` trait.", service.getId().toString(), - ClientDiscoveredEndpointTrait.ID.toString() - ))) + ClientDiscoveredEndpointTrait.ID.toString()), + NO_OPERATIONS + )) .collect(Collectors.toList())); return validationEvents; } diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.errors index be91fa94bda..8dd18d70936 100644 --- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.errors +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/endpoint-error.errors @@ -1,2 +1,2 @@ -[DANGER] ns.foo#FooService: Services SHOULD define an error which indicates an endpoint is invalid. | ClientEndpointDiscovery +[DANGER] ns.foo#FooService: Services SHOULD define an error which indicates an endpoint is invalid. | ClientEndpointDiscovery.MissingErrorDefinition [ERROR] ns.foo#GetObject: The operation `ns.foo#GetObject` is marked with `aws.api#clientDiscoveredEndpoint` and is bound to the service `ns.foo#BarService` but does not have the required error `ns.foo#InvalidEndpointError`. | ClientEndpointDiscovery diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.errors new file mode 100644 index 00000000000..48966869372 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.errors @@ -0,0 +1 @@ +[WARNING] ns.foo#BazService: The service `ns.foo#BazService` is configured to use endpoint discovery, but has no operations bound with the `aws.api#clientDiscoveredEndpoint` trait. | ClientEndpointDiscovery.NoOperations diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.smithy new file mode 100644 index 00000000000..ba6dc7936fb --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/no-operations.smithy @@ -0,0 +1,66 @@ +$version: "2.0" + +namespace ns.foo + +use aws.api#clientEndpointDiscovery +use aws.api#clientDiscoveredEndpoint + +@clientEndpointDiscovery( + operation: DescribeEndpoints, + error: InvalidEndpointError, +) +service BazService { + version: "2021-06-29", + operations: [DescribeEndpoints, GetObjectWithEndpointError], +} + +operation DescribeEndpoints { + input: DescribeEndpointsInput, + output: DescribeEndpointsOutput, +} + +@input +structure DescribeEndpointsInput { + Operation: String, + Identifiers: Identifiers, +} + +map Identifiers { + key: String, + value: String, +} + +@output +structure DescribeEndpointsOutput { + Endpoints: Endpoints, +} + +list Endpoints { + member: Endpoint, +} + +structure Endpoint { + Address: String, + CachePeriodInMinutes: Long, +} + +operation GetObjectWithEndpointError { + input: GetObjectWithEndpointErrorInput, + output: GetObjectWithEndpointErrorOutput, + errors: [InvalidEndpointError], +} + +@input +structure GetObjectWithEndpointErrorInput { + @required + Id: String, +} + +@output +structure GetObjectWithEndpointErrorOutput { + Object: Blob, +} + +@error("client") +@httpError(421) +structure InvalidEndpointError {} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.errors new file mode 100644 index 00000000000..f3954f880cf --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.errors @@ -0,0 +1,2 @@ +[ERROR] ns.foo#GetObject: The operation `ns.foo#GetObject` is marked with `aws.api#clientDiscoveredEndpoint` and is bound to the service `ns.foo#BarService` but does not have the required error `ns.foo#InvalidEndpointError`. | ClientEndpointDiscovery +[ERROR] ns.foo#BarService: The operation `ns.foo#DescribeEndpoints` must be bound to the service `(service: `ns.foo#BarService`)` to use it as the endpoint operation. | ClientEndpointDiscovery.UnboundOperation.DescribeEndpoints diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.smithy new file mode 100644 index 00000000000..083ca4fded5 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/clientendpointdiscovery/unbound-operations.smithy @@ -0,0 +1,97 @@ +$version: "2.0" + +namespace ns.foo + +use aws.api#clientEndpointDiscovery +use aws.api#clientDiscoveredEndpoint + +// This DOES have an error, but it's not bound to the operations. This should +// result in an ERROR. +@clientEndpointDiscovery( + operation: DescribeEndpoints, + error: InvalidEndpointError, +) +service BarService { + version: "2021-06-29", + operations: [GetObject], +} + +// This DOES have an error, and it IS bound to the operations. This should +// not produce any validation events. +@clientEndpointDiscovery( + operation: DescribeEndpoints, + error: InvalidEndpointError, +) +service BazService { + version: "2021-06-29", + operations: [DescribeEndpoints, GetObjectWithEndpointError], +} + +operation DescribeEndpoints { + input: DescribeEndpointsInput, + output: DescribeEndpointsOutput, +} + +@input +structure DescribeEndpointsInput { + Operation: String, + Identifiers: Identifiers, +} + +map Identifiers { + key: String, + value: String, +} + +@output +structure DescribeEndpointsOutput { + Endpoints: Endpoints, +} + +list Endpoints { + member: Endpoint, +} + +structure Endpoint { + Address: String, + CachePeriodInMinutes: Long, +} + +@clientDiscoveredEndpoint(required: true) +operation GetObject { + input: GetObjectInput, + output: GetObjectOutput, +} + +@input +structure GetObjectInput { + @required + Id: String, +} + +@output +structure GetObjectOutput { + Object: Blob, +} + +@clientDiscoveredEndpoint(required: true) +operation GetObjectWithEndpointError { + input: GetObjectWithEndpointErrorInput, + output: GetObjectWithEndpointErrorOutput, + errors: [InvalidEndpointError], +} + +@input +structure GetObjectWithEndpointErrorInput { + @required + Id: String, +} + +@output +structure GetObjectWithEndpointErrorOutput { + Object: Blob, +} + +@error("client") +@httpError(421) +structure InvalidEndpointError {}