Skip to content

Commit

Permalink
Merge pull request #220 from JordonPhillips/fix-endpoint-discovery
Browse files Browse the repository at this point in the history
Properly allow omitting endpoint operation inputs
  • Loading branch information
JordonPhillips committed Dec 2, 2019
2 parents 2a16c8f + 13c06b1 commit 1a962c6
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import software.amazon.smithy.utils.SetUtils;

public class ClientEndpointDiscoveryValidator extends AbstractValidator {
private static final Set<String> VALID_INPUT_MEMBERS = SetUtils.of("Operation", "Identifiers");

@Override
public List<ValidationEvent> validate(Model model) {
ClientEndpointDiscoveryIndex discoveryIndex = model.getKnowledge(ClientEndpointDiscoveryIndex.class);
Expand Down Expand Up @@ -133,37 +135,9 @@ private List<ValidationEvent> validateEndpointOperation(
Model model, OperationIndex opIndex, OperationShape operation
) {
List<ValidationEvent> events = new ArrayList<>();
opIndex.getInput(operation).ifPresent(input -> {
Set<String> memberNames = SetUtils.copyOf(input.getMemberNames());
if (!memberNames.equals(SetUtils.of("Operation", "Identifiers"))) {
events.add(error(input, String.format(
"Input for endpoint discovery operation `%s` may only have the members Operation and "
+ "Identifiers but found: %s",
operation.getId().toString(),
String.join(", ", memberNames)
)));
}

input.getMember("Operation")
.flatMap(member -> model.getShape(member.getTarget()))
.filter(shape -> !shape.isStringShape())
.ifPresent(shape -> events.add(error(
shape, "The Operation member of an endpoint discovery operation must be a string")));

input.getMember("Identifiers")
.map(member -> Pair.of(member, model.getShape(member.getTarget())))
.ifPresent(pair -> {
Optional<MapShape> map = pair.getRight().flatMap(Shape::asMapShape);
if (map.isPresent()) {
Optional<Shape> value = model.getShape(map.get().getValue().getTarget());
if (value.isPresent() && value.get().isStringShape()) {
return;
}
}
events.add(error(pair.getLeft(), "The Identifiers member of an endpoint discovery "
+ "operation must be a map whose keys and values are strings."));
});
});
opIndex.getInput(operation)
.map(input -> validateEndpointOperationInput(model, input, operation))
.ifPresent(events::addAll);

Optional<StructureShape> output = opIndex.getOutput(operation);
if (!output.isPresent()) {
Expand Down Expand Up @@ -224,4 +198,40 @@ private List<ValidationEvent> validateEndpointOperation(

return events;
}

private List<ValidationEvent> validateEndpointOperationInput(
Model model, StructureShape input, OperationShape operation
) {
List<ValidationEvent> events = new ArrayList<>();
Set<String> memberNames = SetUtils.copyOf(input.getMemberNames());
if (!VALID_INPUT_MEMBERS.containsAll(memberNames)) {
events.add(error(input, String.format(
"Input for endpoint discovery operation `%s` may only have the members Operation and "
+ "Identifiers but found: %s",
operation.getId().toString(),
String.join(", ", memberNames)
)));
}

input.getMember("Operation")
.flatMap(member -> model.getShape(member.getTarget()))
.filter(shape -> !shape.isStringShape())
.ifPresent(shape -> events.add(error(
shape, "The Operation member of an endpoint discovery operation must be a string")));

input.getMember("Identifiers")
.map(member -> Pair.of(member, model.getShape(member.getTarget())))
.ifPresent(pair -> {
Optional<MapShape> map = pair.getRight().flatMap(Shape::asMapShape);
if (map.isPresent()) {
Optional<Shape> value = model.getShape(map.get().getValue().getTarget());
if (value.isPresent() && value.get().isStringShape()) {
return;
}
}
events.add(error(pair.getLeft(), "The Identifiers member of an endpoint discovery "
+ "operation must be a map whose keys and values are strings."));
});
return events;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"smithy": "0.4.0",
"ns.foo": {
"shapes": {
"FooService": {
"type": "service",
"version": "2019-09-10",
"aws.api#clientEndpointDiscovery": {
"operation": "ns.foo#DescribeEndpoints",
"error": "ns.foo#InvalidEndpointError"
},
"operations": [
"DescribeEndpoints",
"GetObject",
"PutObject"
]
},
"BarService": {
"type": "service",
"version": "2019-09-10",
"operations": [
"DescribeEndpoints"
]
},
"DescribeEndpoints": {
"type": "operation",
"input": "DescribeEndpointsInput",
"output": "DescribeEndpointsOutput"
},
"DescribeEndpointsInput": {
"type": "structure"
},
"DescribeEndpointsOutput": {
"type": "structure",
"members": {
"Endpoints": {
"target": "Endpoints"
}
}
},
"Endpoints": {
"type": "list",
"member": {
"target": "Endpoint"
}
},
"Endpoint": {
"type": "structure",
"members": {
"Address": {
"target": "smithy.api#String"
},
"CachePeriodInMinutes": {
"target": "smithy.api#Long"
}
}
},
"GetObject": {
"type": "operation",
"input": "GetObjectInput",
"output": "GetObjectOutput",
"aws.api#clientDiscoveredEndpoint": {
"required": true
},
"errors": ["InvalidEndpointError"]
},
"GetObjectInput": {
"type": "structure",
"members": {
"Id": {
"target": "smithy.api#String",
"required": true
}
}
},
"GetObjectOutput": {
"type": "structure",
"members": {
"Object": {
"target": "smithy.api#Blob"
}
}
},
"PutObject": {
"type": "operation",
"input": "PutObjectInput",
"aws.api#clientDiscoveredEndpoint": {
"required": false
},
"errors": ["InvalidEndpointError"]
},
"PutObjectInput": {
"type": "structure",
"members": {
"Id": {
"target": "smithy.api#String",
"required": true
},
"Object": {
"target": "smithy.api#Blob"
}
}
},
"InvalidEndpointError": {
"type": "structure",
"error": "client",
"httpError": 421
}
}
}
}

0 comments on commit 1a962c6

Please sign in to comment.