Skip to content

Commit

Permalink
Merge branch 'main' into filter-deprecated
Browse files Browse the repository at this point in the history
  • Loading branch information
hpmellema authored Nov 13, 2024
2 parents cd9d8fe + 8da7457 commit 9d9aa76
Show file tree
Hide file tree
Showing 20 changed files with 322 additions and 53 deletions.
9 changes: 5 additions & 4 deletions docs/source-2.0/aws/aws-iam.rst
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,11 @@ Value type
``map`` of IAM identifiers to condition key ``structure``

The ``aws.iam#defineConditionKeys`` trait defines additional condition keys
that appear within a service. Keys in the map must be valid IAM identifiers,
meaning they must adhere to the following regular expression:
``"^([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:[^:]+)$"``.
Each condition key structure supports the following members:
that appear within a service. Keys in the map must be valid IAM identifiers
or names of condition keys, meaning they must adhere to the following regular
expression: ``"^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$"``. If only a
condition key name is specified, the service is inferred to be the
``arnNamespace``. Each condition key structure supports the following members:

.. list-table::
:header-rows: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.KnowledgeIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
Expand All @@ -47,34 +48,38 @@ public final class ConditionKeysIndex implements KnowledgeIndex {
private final Map<ShapeId, Map<ShapeId, Set<String>>> resourceConditionKeys = new HashMap<>();

public ConditionKeysIndex(Model model) {
model.shapes(ServiceShape.class).forEach(service -> {
service.getTrait(ServiceTrait.class).ifPresent(trait -> {
// Copy over the explicitly defined condition keys into the service map.
// This will be mutated when adding inferred resource condition keys.
serviceConditionKeys.put(service.getId(), new HashMap<>(
service.getTrait(DefineConditionKeysTrait.class)
.map(DefineConditionKeysTrait::getConditionKeys)
.orElse(MapUtils.of())));
resourceConditionKeys.put(service.getId(), new HashMap<>());
for (ServiceShape service : model.getServiceShapesWithTrait(ServiceTrait.class)) {
// Defines the scoping of any derived condition keys.
String arnNamespace = service.expectTrait(ServiceTrait.class).getArnNamespace();

// Defines the scoping of any derived condition keys.
String arnRoot = trait.getArnNamespace();
// Copy over the explicitly defined condition keys into the service map.
// This will be mutated when adding inferred resource condition keys.
Map<String, ConditionKeyDefinition> serviceKeys = new HashMap<>();
if (service.hasTrait(DefineConditionKeysTrait.ID)) {
DefineConditionKeysTrait trait = service.expectTrait(DefineConditionKeysTrait.class);
for (Map.Entry<String, ConditionKeyDefinition> entry : trait.getConditionKeys().entrySet()) {
// If no colon is present, we infer that this condition key is for the
// current service and apply its ARN namespace.
String key = entry.getKey();
if (!key.contains(":")) {
key = arnNamespace + ":" + key;
}
serviceKeys.put(key, entry.getValue());
}
}
serviceConditionKeys.put(service.getId(), serviceKeys);
resourceConditionKeys.put(service.getId(), new HashMap<>());

// Compute the keys of child resources.
service.getResources().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(resource -> {
compute(model, service, arnRoot, resource, null);
});
// Compute the keys of child resources.
for (ShapeId resourceId : service.getResources()) {
compute(model, service, arnNamespace, model.expectShape(resourceId, ResourceShape.class), null);
}

// Compute the keys of operations of the service.
service.getOperations().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(operation -> {
compute(model, service, arnRoot, operation, null);
});
});
});
// Compute the keys of operations of the service.
for (ShapeId operationId : service.getOperations()) {
compute(model, service, arnNamespace, model.expectShape(operationId, OperationShape.class), null);
}
}
}

public static ConditionKeysIndex of(Model model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ string conditionKeyValue
/// inferred and global condition keys.
@trait(selector: "service")
map defineConditionKeys {
key: IamIdentifier
key: ConditionKeyName
value: ConditionKeyDefinition
}

Expand Down Expand Up @@ -156,6 +156,10 @@ list ResourceNameList {
member: ResourceName
}

@private
@pattern("^(([A-Za-z0-9][A-Za-z0-9-\\.]{0,62}:)?[^:\\s]+)$")
string ConditionKeyName

/// The IAM policy type of the value that will supplied for this context key
@private
enum ConditionKeyType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public void successfullyLoadsConditionKeys() {

ConditionKeysIndex index = ConditionKeysIndex.of(model);
assertThat(index.getConditionKeyNames(service), containsInAnyOrder(
"aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2"));
"aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2", "myservice:bar"));
assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Operation1")),
containsInAnyOrder("aws:accountId", "foo:baz"));
containsInAnyOrder("aws:accountId", "myservice:bar"));
assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
containsInAnyOrder("aws:accountId", "foo:baz", "myservice:Resource1Id1"));
// Note that ID1 is not duplicated but rather reused on the child operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void loadsFromModel() {
DefineConditionKeysTrait trait = shape.expectTrait(DefineConditionKeysTrait.class);
assertEquals(3,trait.getConditionKeys().size());
assertFalse(trait.getConditionKey("myservice:Bar").get().isRequired());
assertFalse(trait.getConditionKey("myservice:Foo").get().isRequired());
assertFalse(trait.getConditionKey("Foo").get().isRequired());
assertTrue(trait.getConditionKey("myservice:Baz").get().isRequired());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use aws.iam#serviceResolvedConditionKeys
externalDocumentation: "http://baz.com"
required: true
}
"myservice:Foo": {
"Foo": {
type: "String"
documentation: "The Foo string"
externalDocumentation: "http://foo.com"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$version: "1.0"
$version: "2.0"
namespace smithy.example

use aws.api#arnReference
Expand All @@ -8,39 +8,44 @@ use aws.iam#defineConditionKeys
use aws.iam#disableConditionKeyInference
use aws.iam#iamResource

@service(sdkId: "My")
@service(sdkId: "My", arnNamespace: "myservice")
@defineConditionKeys(
"foo:baz": {
type: "String",
documentation: "Foo baz",
type: "String"
documentation: "Foo baz"
relativeDocumentation: "condition-keys.html"
}
"bar": {
type: "String"
documentation: "Foo bar"
relativeDocumentation: "condition-keys.html"
}
)
service MyService {
version: "2019-02-20",
operations: [Operation1],
version: "2019-02-20"
operations: [Operation1]
resources: [Resource1]
}

@conditionKeys(["aws:accountId", "foo:baz"])
@conditionKeys(["aws:accountId", "myservice:bar"])
operation Operation1 {}

@conditionKeys(["aws:accountId", "foo:baz"])
resource Resource1 {
identifiers: {
id1: ArnString,
},
id1: ArnString
}
resources: [Resource2, Resource3, Resource4]
}

@iamResource(name: "ResourceTwo")
resource Resource2 {
identifiers: {
id1: ArnString,
id2: FooString,
},
read: GetResource2,
list: ListResource2,
id1: ArnString
id2: FooString
}
read: GetResource2
list: ListResource2
}

@disableConditionKeyInference
Expand Down Expand Up @@ -71,7 +76,7 @@ operation GetResource2 {

structure GetResource2Input {
@required
id1: ArnString,
id1: ArnString

@required
id2: FooString
Expand All @@ -82,13 +87,13 @@ string FooString

@readonly
operation ListResource2 {
input: ListResource2Input,
input: ListResource2Input
output: ListResource2Output
}

structure ListResource2Input {
@required
id1: ArnString,
id1: ArnString
}

structure ListResource2Output {}
Expand Down
2 changes: 2 additions & 0 deletions smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ structure XmlListsOutput {
@xmlName("renamed")
renamedListMembers: RenamedListMembers,

@suppress(["XmlFlattenedTrait"])
@xmlFlattened
// The xmlname on the targeted list is ignored, and the member name is used.
// The validation that flags this is suppressed so we can test this behavior.
flattenedList: RenamedListMembers,

@xmlName("customName")
Expand Down
2 changes: 2 additions & 0 deletions smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ structure XmlListsOutput {
@xmlName("renamed")
renamedListMembers: RenamedListMembers,

@suppress(["XmlFlattenedTrait"])
@xmlFlattened
// The xmlname on the targeted list is ignored, and the member name is used.
// The validation that flags this is suppressed so we can test this behavior.
flattenedList: RenamedListMembers,

@xmlName("customName")
Expand Down
5 changes: 5 additions & 0 deletions smithy-aws-protocol-tests/model/restXml/document-lists.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,10 @@ structure XmlListsInputOutput {
@xmlName("renamed")
renamedListMembers: RenamedListMembers,

@suppress(["XmlFlattenedTrait"])
@xmlFlattened
// The xmlname on the targeted list is ignored, and the member name is used.
// The validation that flags this is suppressed so we can test this behavior.
flattenedList: RenamedListMembers,

@xmlName("customName")
Expand All @@ -376,7 +378,10 @@ structure XmlListsInputOutput {
@xmlName("myStructureList")
structureList: StructureList,

@suppress(["XmlFlattenedTrait"])
@xmlFlattened
// The xmlname on the targeted list is ignored, and the member name is used.
// The validation that flags this is suppressed so we can test this behavior.
flattenedStructureList: StructureList
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@ public void changeStringEnumsToEnumShapes(boolean synthesizeEnumNames) {
});
}

/**
* Flattens service-level pagination information into operation pagination traits.
*
* @see ModelTransformer#flattenPaginationInfoIntoOperations(Model, ServiceShape)
*/
public void flattenPaginationInfoIntoOperations() {
transforms.add((model, transformer) -> {
LOGGER.finest("Flattening pagination info into operation traits for directed codegen");
return transformer.flattenPaginationInfoIntoOperations(model,
model.expectShape(service, ServiceShape.class));
});
}

/**
* Sets the shapes order for code generation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ private StructureShape renameShapeIfNeeded(
return struct;
}

LOGGER.info(() -> "Renaming " + struct.getId() + " to " + expectedName);
LOGGER.fine(() -> "Renaming " + struct.getId() + " to " + expectedName);
ShapeId newId = createSyntheticShapeId(model, operation, suffix);

return struct.toBuilder()
Expand Down Expand Up @@ -229,7 +229,7 @@ private static ShapeId createSyntheticShapeId(
private static ShapeId resolveConflict(ShapeId id, String suffix) {
// Say GetFooRequest exists. This then returns GetFooOperationRequest.
String updatedName = id.getName().replace(suffix, "Operation" + suffix);
LOGGER.info(() -> "Deconflicting synthetic ID from " + id + " to use name " + updatedName);
LOGGER.fine(() -> "Deconflicting synthetic ID from " + id + " to use name " + updatedName);
return ShapeId.fromParts(id.getNamespace(), updatedName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.model.transform;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.PaginatedIndex;
import software.amazon.smithy.model.knowledge.PaginationInfo;
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.traits.PaginatedTrait;

/**
* Flattens pagination info from service shapes into operation-level pagination traits.
*/
final class FlattenPaginationInfo {

private final ServiceShape service;

FlattenPaginationInfo(ServiceShape service) {
this.service = service;
}

public Model transform(ModelTransformer transformer, Model model) {
Optional<PaginatedTrait> serviceLevelPagination = service.getTrait(PaginatedTrait.class);
if (!serviceLevelPagination.isPresent()) {
return model;
}
PaginatedIndex paginatedIndex = PaginatedIndex.of(model);

// Merge service-level information into each operation's pagination trait.
Set<Shape> updatedShapes = new HashSet<>();
for (OperationShape operationShape : model.getOperationShapesWithTrait(PaginatedTrait.class)) {
PaginationInfo paginationInfo = paginatedIndex.getPaginationInfo(service, operationShape).get();
OperationShape updatedShape = operationShape.toBuilder()
.addTrait(paginationInfo.getPaginatedTrait())
.build();
updatedShapes.add(updatedShape);
}

// Remove the paginated trait from the service as it's info has been flattened into the operations
updatedShapes.add(service.toBuilder().removeTrait(PaginatedTrait.ID).build());

return transformer.replaceShapes(model, updatedShapes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -710,4 +710,14 @@ public Model filterDeprecatedRelativeDate(Model model, String relativeDate) {
public Model filterDeprecatedRelativeVersion(Model model, String relativeVersion) {
return new FilterDeprecatedRelativeVersion(relativeVersion).transform(this, model);
}

/**
* Flattens all service-level pagination information into operation-level pagination traits.
*
* @param model Model to transform.
* @return Returns the transformed model.
*/
public Model flattenPaginationInfoIntoOperations(Model model, ServiceShape forService) {
return new FlattenPaginationInfo(forService).transform(this, model);
}
}
Loading

0 comments on commit 9d9aa76

Please sign in to comment.