-
Notifications
You must be signed in to change notification settings - Fork 221
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restrict httpPayload targets for AWS protocols
The AWS protocols do not currently support http payloads that target sets, lists, or maps. This adds validators to ensure that such bindings do not get used.
- Loading branch information
1 parent
2ba1e6a
commit 63b5277
Showing
4 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
...c/main/java/software/amazon/smithy/aws/traits/protocols/ProtocolHttpPayloadValidator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* 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.aws.traits.protocols; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import software.amazon.smithy.model.Model; | ||
import software.amazon.smithy.model.knowledge.HttpBinding; | ||
import software.amazon.smithy.model.knowledge.HttpBinding.Location; | ||
import software.amazon.smithy.model.knowledge.HttpBindingIndex; | ||
import software.amazon.smithy.model.knowledge.ServiceIndex; | ||
import software.amazon.smithy.model.knowledge.TopDownIndex; | ||
import software.amazon.smithy.model.shapes.OperationShape; | ||
import software.amazon.smithy.model.shapes.ServiceShape; | ||
import software.amazon.smithy.model.shapes.ShapeId; | ||
import software.amazon.smithy.model.shapes.ShapeType; | ||
import software.amazon.smithy.model.shapes.ToShapeId; | ||
import software.amazon.smithy.model.traits.Trait; | ||
import software.amazon.smithy.model.validation.AbstractValidator; | ||
import software.amazon.smithy.model.validation.ValidationEvent; | ||
import software.amazon.smithy.utils.SetUtils; | ||
import software.amazon.smithy.utils.SmithyInternalApi; | ||
|
||
/** | ||
* Ensures that the http payload trait is only bound to structures, unions, | ||
* documents, blobs, or strings for AWS protocols. | ||
*/ | ||
@SmithyInternalApi | ||
public final class ProtocolHttpPayloadValidator extends AbstractValidator { | ||
private static final Set<ShapeType> VALID_HTTP_PAYLOAD_TYPES = SetUtils.of( | ||
ShapeType.STRUCTURE, ShapeType.UNION, ShapeType.DOCUMENT, ShapeType.BLOB, ShapeType.STRING | ||
); | ||
|
||
@Override | ||
public List<ValidationEvent> validate(Model model) { | ||
ServiceIndex serviceIndex = ServiceIndex.of(model); | ||
HttpBindingIndex bindingIndex = HttpBindingIndex.of(model); | ||
TopDownIndex topDownIndex = TopDownIndex.of(model); | ||
return model.shapes(ServiceShape.class) | ||
.filter(service -> usesAwsProtocol(service, serviceIndex)) | ||
.flatMap(service -> validateService(model, service, bindingIndex, topDownIndex).stream()) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private boolean usesAwsProtocol(ServiceShape service, ServiceIndex index) { | ||
for (Trait protocol : index.getProtocols(service).values()) { | ||
if (protocol instanceof AwsProtocolTrait) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private List<ValidationEvent> validateService( | ||
Model model, | ||
ServiceShape service, | ||
HttpBindingIndex bindingIndex, | ||
TopDownIndex topDownIndex | ||
) { | ||
List<ValidationEvent> events = new ArrayList<>(); | ||
|
||
for (OperationShape operation : topDownIndex.getContainedOperations(service)) { | ||
List<HttpBinding> requestBindings = bindingIndex.getRequestBindings(operation, Location.PAYLOAD); | ||
validateBindings(model, requestBindings).ifPresent(events::add); | ||
|
||
List<HttpBinding> responseBindings = bindingIndex.getResponseBindings(operation, Location.PAYLOAD); | ||
validateBindings(model, responseBindings).ifPresent(events::add); | ||
|
||
for (ShapeId error : operation.getErrors()) { | ||
List<HttpBinding> errorBindings = bindingIndex.getResponseBindings(error, Location.PAYLOAD); | ||
validateBindings(model, errorBindings).ifPresent(events::add); | ||
} | ||
} | ||
|
||
return events; | ||
} | ||
|
||
private Optional<ValidationEvent> validateBindings(Model model, Collection<HttpBinding> payloadBindings) { | ||
for (HttpBinding binding : payloadBindings) { | ||
if (!payloadBoundToValidType(model, binding.getMember().getTarget())) { | ||
return Optional.of(error(binding.getMember(), "AWS Protocols do not support applying the httpPayload " | ||
+ "trait to members that target sets, lists, or maps.")); | ||
} | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
private boolean payloadBoundToValidType(Model model, ToShapeId payloadShape) { | ||
return model.getShape(payloadShape.toShapeId()) | ||
.map(shape -> VALID_HTTP_PAYLOAD_TYPES.contains(shape.getType())) | ||
.orElse(false); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
...aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.errors
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[ERROR] smithy.example#InvalidBindingInput$listBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload | ||
[ERROR] smithy.example#InvalidBindingOutput$mapBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload | ||
[ERROR] smithy.example#InvalidBindingError$setBinding: AWS Protocols do not support applying the httpPayload trait to members that target sets, lists, or maps. | ProtocolHttpPayload |
50 changes: 50 additions & 0 deletions
50
...aws/traits/errorfiles/protocols/aws-protocols-do-not-support-list-set-map-payloads.smithy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// AWS protocols do not currently support applying the http payload trait to | ||
// sets, lists, or maps. | ||
|
||
namespace smithy.example | ||
|
||
use aws.protocols#restJson1 | ||
use smithy.api#http | ||
use smithy.api#httpPayload | ||
|
||
@restJson1 | ||
service InvalidExample { | ||
version: "2020-12-29", | ||
operations: [InvalidBindingOperation], | ||
} | ||
|
||
@http(method: "POST", uri: "/invalid-payload") | ||
operation InvalidBindingOperation { | ||
input: InvalidBindingInput, | ||
output: InvalidBindingOutput, | ||
errors: [InvalidBindingError], | ||
} | ||
|
||
structure InvalidBindingInput { | ||
@httpPayload | ||
listBinding: StringList, | ||
} | ||
|
||
structure InvalidBindingOutput { | ||
@httpPayload | ||
mapBinding: StringMap, | ||
} | ||
|
||
@error("client") | ||
structure InvalidBindingError { | ||
@httpPayload | ||
setBinding: StringSet | ||
} | ||
|
||
list StringList { | ||
member: String | ||
} | ||
|
||
set StringSet { | ||
member: String | ||
} | ||
|
||
map StringMap { | ||
key: String, | ||
value: String, | ||
} |