diff --git a/docs/source/spec/core.rst b/docs/source/spec/core.rst index 37e98f4025d..f75c81e2cb6 100644 --- a/docs/source/spec/core.rst +++ b/docs/source/spec/core.rst @@ -4740,7 +4740,7 @@ The following example defines a service that supports both the hypothetical "type": "structure", "traits": { "smithy.api#documentation": "An example JSON protocol." - "smithy.api#protocolDefinition": true, + "smithy.api#protocolDefinition": {}, "smithy.api#trait": { "selector": "service" } @@ -4750,7 +4750,7 @@ The following example defines a service that supports both the hypothetical "type": "structure", "traits": { "smithy.api#documentation": "An example JSON protocol." - "smithy.api#protocolDefinition": true, + "smithy.api#protocolDefinition": {}, "smithy.api#trait": { "selector": "service" } @@ -4942,7 +4942,21 @@ Summary Trait selector ``[trait|trait]`` Value type - Annotation trait. + An object with the following properties: + + .. list-table:: + :header-rows: 1 + :widths: 10 23 67 + + * - Property + - Type + - Description + * - traits + - [:ref:`shape-id`] + - List of shape IDs that auth scheme implementations MUST + understand in order to successfully use the scheme. Each shape + MUST exist and MUST be a trait. Code generators SHOULD ensure + that they support each listed trait. Every operation in the closure of a service is expected to support the authentication schemes applied to a service unless the service or operation @@ -4984,7 +4998,7 @@ and the hypothetical ``fooExample`` authentication scheme. "smithy.example#fooExample": { "type": "structure", "traits": { - "smithy.api#authDefinition": true, + "smithy.api#authDefinition": {}, "smithy.api#trait": { "selector": "service" } diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.json b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.json index 4d23d47359c..269e4be352e 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.json +++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.json @@ -17,7 +17,11 @@ "smithy.api#trait": { "selector": "service" }, - "smithy.api#authDefinition": true, + "smithy.api#authDefinition": { + "traits": [ + "aws.auth#unsignedPayload" + ] + }, "smithy.api#documentation": "Signature Version 4 is the process to add authentication information to AWS requests sent by HTTP. For security, most requests to AWS must be signed with an access key, which consists of an access key ID and secret access key. These two keys are commonly referred to as your security credentials.", "smithy.api#externalDocumentation": "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" } diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json index 2b6e79744f3..1416e017f2d 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json +++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.json @@ -131,7 +131,7 @@ }, "smithy.api#protocolDefinition": { "traits": [ - "aws.api#ec2QueryName", + "aws.protocols#ec2QueryName", "smithy.api#xmlAttribute", "smithy.api#xmlFlattened", "smithy.api#xmlName", diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthDefinitionTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthDefinitionTrait.java index 714029a703c..b18abc96f7f 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthDefinitionTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthDefinitionTrait.java @@ -15,27 +15,96 @@ package software.amazon.smithy.model.traits; -import software.amazon.smithy.model.SourceLocation; +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.ToSmithyBuilder; /** * This trait is attached to another trait to define an auth scheme. */ -public final class AuthDefinitionTrait extends BooleanTrait { +public final class AuthDefinitionTrait extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("smithy.api#authDefinition"); + private final List traits; - public AuthDefinitionTrait(SourceLocation sourceLocation) { - super(ID, sourceLocation); + public AuthDefinitionTrait(Builder builder) { + super(ID, builder.getSourceLocation()); + traits = ListUtils.copyOf(builder.traits); } - public AuthDefinitionTrait() { - this(SourceLocation.NONE); + /** + * Gets the list of shape IDs that auth implementations must know about + * in order to successfully utilize the auth scheme. + * + * @return Returns the auth traits. + */ + public List getTraits() { + return traits; } - public static final class Provider extends BooleanTrait.Provider { + public static Builder builder() { + return new Builder(); + } + + @Override + protected Node createNode() { + if (traits.isEmpty()) { + return Node.objectNode(); + } + + ArrayNode ids = traits.stream() + .map(ShapeId::toString) + .map(Node::from) + .collect(ArrayNode.collect()); + + return Node.objectNode().withMember("traits", ids); + } + + @Override + public Builder toBuilder() { + return builder().sourceLocation(getSourceLocation()).traits(traits); + } + + public static final class Provider extends AbstractTrait.Provider { public Provider() { - super(ID, AuthDefinitionTrait::new); + super(ID); + } + + @Override + public AuthDefinitionTrait createTrait(ShapeId target, Node value) { + Builder builder = builder().sourceLocation(value); + ObjectNode objectNode = value.expectObjectNode(); + objectNode.getArrayMember("traits").ifPresent(traits -> { + for (String string : Node.loadArrayOfString("traits", traits)) { + builder.addTrait(ShapeId.from(string)); + } + }); + return builder.build(); + } + } + + public static final class Builder extends AbstractTraitBuilder { + private final List traits = new ArrayList<>(); + + @Override + public AuthDefinitionTrait build() { + return new AuthDefinitionTrait(this); + } + + public Builder traits(List traits) { + this.traits.clear(); + this.traits.addAll(traits); + return this; + } + + public Builder addTrait(ShapeId trait) { + traits.add(trait); + return this; } } } diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy index e5dadc72ad5..e9effccc84c 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude-traits.smithy @@ -83,7 +83,11 @@ string TraitShapeId /// shapes, must be a structure, and must have the `trait` trait. @trait(selector: "structure[trait|trait]") @tags(["diff.error.add", "diff.error.remove"]) -structure authDefinition {} +structure authDefinition { + /// Defines a list of traits that auth implementations must + /// understand in order to successfully use the scheme. + traits: TraitShapeIdList, +} /// Enables HTTP Basic Authentication as defined in RFC 2617 /// on a service or operation. diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/AuthDefinitionTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/AuthDefinitionTraitTest.java new file mode 100644 index 00000000000..b3073c2138d --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/AuthDefinitionTraitTest.java @@ -0,0 +1,36 @@ +package software.amazon.smithy.model.traits; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; + +public class AuthDefinitionTraitTest { + @Test + public void loadsTrait() { + TraitFactory provider = TraitFactory.createServiceFactory(); + ArrayNode values = Node.fromStrings( + JsonNameTrait.ID.toString(), + XmlNameTrait.ID.toString()); + Node node = Node.objectNode().withMember("traits", values); + Optional trait = provider.createTrait( + ShapeId.from("smithy.api#authDefinition"), + ShapeId.from("ns.qux#foo"), + node); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(AuthDefinitionTrait.class)); + AuthDefinitionTrait authDefinitionTrait = (AuthDefinitionTrait) trait.get(); + assertThat(authDefinitionTrait.getTraits(), containsInAnyOrder( + JsonNameTrait.ID, XmlNameTrait.ID)); + assertThat(authDefinitionTrait.toNode(), equalTo(node)); + assertThat(authDefinitionTrait.toBuilder().build(), equalTo(authDefinitionTrait)); + } +}