From a28f2333265bb8a7ba32894b6191c2f44578c6f2 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 24 Jun 2020 10:46:23 -0700 Subject: [PATCH] Add document validation and fix their HTTP binding HTTP binding traits used invalid selectors that aren't actually supported, particularly around document types. Document types today can only be bound in the payload of an HTTP request. This change also add a flag to protocol trait definitions that defines if the protocol supports inline document types (along with built-in validation to detect when a protcool says it doesn't support document types but a service uses document types with the protocol). HTTP bindings previously stated that only "application/json" media types found in HTTP headers are base64 encoded, however any string with a mediaType needs to be base64 encoded. --- .../1.0/spec/aws/aws-ec2-query-protocol.rst | 4 + .../1.0/spec/aws/aws-query-protocol.rst | 4 + .../1.0/spec/aws/aws-restxml-protocol.rst | 4 + docs/source/1.0/spec/core/http-traits.rst | 18 +-- docs/source/1.0/spec/core/protocol-traits.rst | 12 +- .../model/awsJson1_1/documents.smithy | 62 +++++++++ .../model/awsJson1_1/main.smithy | 1 + .../model/restJson1/documents.smithy | 128 ++++++++++++++++++ .../model/restJson1/main.smithy | 4 + .../META-INF/smithy/aws.protocols.json | 3 + ...-protocols-do-not-support-documents.errors | 2 + ...-protocols-do-not-support-documents.smithy | 23 ++++ .../model/traits/ProtocolDefinitionTrait.java | 41 +++++- .../NoInlineDocumentSupportValidator.java | 104 ++++++++++++++ ...e.amazon.smithy.model.validation.Validator | 1 + .../smithy/model/loader/prelude-traits.smithy | 12 +- .../no-inline-document-support.errors | 1 + .../no-inline-document-support.smithy | 32 +++++ 18 files changed, 439 insertions(+), 17 deletions(-) create mode 100644 smithy-aws-protocol-tests/model/awsJson1_1/documents.smithy create mode 100644 smithy-aws-protocol-tests/model/restJson1/documents.smithy create mode 100644 smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.errors create mode 100644 smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/NoInlineDocumentSupportValidator.java create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.errors create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy diff --git a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst index d5cf1c21ab2..22963f915f2 100644 --- a/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-ec2-query-protocol.rst @@ -27,6 +27,10 @@ Trait selector Value type Annotation trait. +.. important:: + + This protocol does not support :ref:`inline document types `. + .. tabs:: .. code-tab:: smithy diff --git a/docs/source/1.0/spec/aws/aws-query-protocol.rst b/docs/source/1.0/spec/aws/aws-query-protocol.rst index 83e07a835c4..928680a0e08 100644 --- a/docs/source/1.0/spec/aws/aws-query-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-query-protocol.rst @@ -56,4 +56,8 @@ See } } +.. important:: + + This protocol does not support :ref:`inline document types `. + *TODO: Add specifications, protocol examples, etc.* diff --git a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst index f8eddcec71a..52bae86ac05 100644 --- a/docs/source/1.0/spec/aws/aws-restxml-protocol.rst +++ b/docs/source/1.0/spec/aws/aws-restxml-protocol.rst @@ -192,6 +192,10 @@ that affect serialization: * - :ref:`timestampFormat ` - Defines a custom timestamp serialization format. +.. important:: + + This protocol does not support :ref:`inline document types `. + ------------ Content-Type diff --git a/docs/source/1.0/spec/core/http-traits.rst b/docs/source/1.0/spec/core/http-traits.rst index 6d86fabf210..7803639435b 100644 --- a/docs/source/1.0/spec/core/http-traits.rst +++ b/docs/source/1.0/spec/core/http-traits.rst @@ -500,9 +500,7 @@ Serialization rules: as a separate HTTP header either by concatenating the values with a comma on a single line or by serializing each header value on its own line. * boolean values are serialized as ``true`` or ``false``. -* blob values are base-64 encoded. -* string values with a :ref:`mediaType-trait` of "application/json" or that - end in "+json" are base-64 encoded. +* string values with a :ref:`mediaType-trait` are base64 encoded. * timestamp values are serialized using the ``http-date`` format as defined in the ``IMF-fixdate`` production of :rfc:`7231#section-7.1.1.1`. @@ -688,9 +686,11 @@ Trait selector .. code-block:: none structure > member - :test(> map > member[id|member=value] > :test(simpleType, collection > member > simpleType)) + :test(> map > member[id|member=value] > :test( + boolean, number, string, timestamp, + collection > member > :test(boolean, number, string, timestamp))) - *Structure member that targets a map of simple types or a map of lists/sets of simple types* + *Structure member that targets a map of specific simple types or a map of lists/sets of specific simple types* Value type ``string`` value that defines the prefix to prepend to each header field name stored in the targeted map member. For example, given a prefix value @@ -762,9 +762,11 @@ Summary Trait selector .. code-block:: none - structure > :test(member > :test(simpleType, collection > member > simpleType)) + structure > member + :test(> simpleType:not(document), + > collection > member > simpleType:not(document))) - *Structure members that target simple types or lists/sets of simple types* + *Structure members that target non-document simple types or collections of non-document simple types* Value type ``string`` value defining the name of the query string parameter. The query string value MUST NOT be empty. This trait is ignored when @@ -786,7 +788,7 @@ Serialization rules: * Multiple members of a structure MUST NOT case-sensitively target the same query string parameter. * boolean values are serialized as ``true`` or ``false``. -* blob values are base-64 encoded when serialized in the query string. +* blob values are base64 encoded when serialized in the query string. * timestamp values are serialized as an :rfc:`3339` ``date-time`` string (e.g., ``1990-12-31T23:59:60Z``). * :ref:`list` members are serialized by adding multiple query string parameters diff --git a/docs/source/1.0/spec/core/protocol-traits.rst b/docs/source/1.0/spec/core/protocol-traits.rst index ab7080b9d9c..8d5c9eb7dc3 100644 --- a/docs/source/1.0/spec/core/protocol-traits.rst +++ b/docs/source/1.0/spec/core/protocol-traits.rst @@ -41,6 +41,12 @@ Value type in order to successfully use the protocol. Each shape MUST exist and MUST be a trait. Code generators SHOULD ensure that they support each listed trait. + * - noInlineDocumentSupport + - ``boolean`` + - If set to ``true``, indicates that this protocol does not support + :ref:`document ` shapes. A service that uses such + a protocol MUST NOT contain any document shapes in their service + closure. Smithy is protocol agnostic, which means it focuses on the interfaces and abstractions that are provided to end-users rather than how the data is sent @@ -275,5 +281,7 @@ Smithy defines the following built-in timestamp formats: a timestamp differs from the default protocol format. Using this trait too liberally can cause other tooling to improperly interpret the timestamp. -See :ref:`timestamp-serialization-format` for information on how to -determine the serialization format of a timestamp. +.. seealso:: + + Refer to :ref:`timestamp-serialization-format` for information on how to + determine the serialization format of a timestamp. diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/documents.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/documents.smithy new file mode 100644 index 00000000000..8f96e8746e2 --- /dev/null +++ b/smithy-aws-protocol-tests/model/awsJson1_1/documents.smithy @@ -0,0 +1,62 @@ +// This file defines test cases that serialize inline documents. + +$version: "1.0" + +namespace aws.protocoltests.json + +use aws.protocols#awsJson1_1 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +/// This example serializes an inline document as part of the payload. +operation PutAndGetInlineDocuments { + input: PutAndGetInlineDocumentsInputOutput, + output: PutAndGetInlineDocumentsInputOutput +} + +structure PutAndGetInlineDocumentsInputOutput { + inlineDocument: Document +} + +document Document + +apply PutAndGetInlineDocuments @httpRequestTests([ + { + id: "PutAndGetInlineDocumentsInput", + documentation: "Serializes inline documents in a JSON request.", + protocol: awsJson1_1, + method: "POST", + uri: "/", + body: """ + { + "inlineDocument": {"foo": "bar"} + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + inlineDocument: { + foo: "bar" + } + } + } +]) + +apply PutAndGetInlineDocuments @httpResponseTests([ + { + id: "PutAndGetInlineDocumentsInput", + documentation: "Serializes inline documents in a JSON response.", + protocol: awsJson1_1, + code: 200, + body: """ + { + "inlineDocument": {"foo": "bar"} + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + inlineDocument: { + foo: "bar" + } + } + } +]) diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy index af00d2c7ecf..d30579d8bac 100644 --- a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy +++ b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy @@ -23,6 +23,7 @@ service JsonProtocol { EmptyOperation, KitchenSinkOperation, OperationWithOptionalInputOutput, + PutAndGetInlineDocuments ], } diff --git a/smithy-aws-protocol-tests/model/restJson1/documents.smithy b/smithy-aws-protocol-tests/model/restJson1/documents.smithy new file mode 100644 index 00000000000..da66bd3fed1 --- /dev/null +++ b/smithy-aws-protocol-tests/model/restJson1/documents.smithy @@ -0,0 +1,128 @@ +// This file defines test cases that serialize inline documents. + +$version: "1.0" + +namespace aws.protocoltests.restjson + +use aws.protocols#restJson1 +use smithy.test#httpRequestTests +use smithy.test#httpResponseTests + +// Define some shapes shared throughout these test cases. +document Document + +/// This example serializes an inline document as part of the payload. +@idempotent +@http(uri: "/InlineDocument", method: "PUT") +operation InlineDocument { + input: InlineDocumentInputOutput, + output: InlineDocumentInputOutput +} + +structure InlineDocumentInputOutput { + stringValue: String, + documentValue: Document, +} + +apply InlineDocument @httpRequestTests([ + { + id: "InlineDocumentInput", + documentation: "Serializes inline documents as part of the JSON request payload with no escaping.", + protocol: restJson1, + method: "PUT", + uri: "/InlineDocument", + body: """ + { + "stringValue": "string", + "documentValue": { + "foo": "bar" + } + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + stringValue: "string", + documentValue: { + foo: "bar" + } + } + } +]) + +apply InlineDocument @httpResponseTests([ + { + id: "InlineDocumentOutput", + documentation: "Serializes inline documents as part of the JSON response payload with no escaping.", + protocol: restJson1, + code: 200, + body: """ + { + "stringValue": "string", + "documentValue": { + "foo": "bar" + } + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + stringValue: "string", + documentValue: { + foo: "bar" + } + } + } +]) + +/// This example serializes an inline document as the entire HTTP payload. +@idempotent +@http(uri: "/InlineDocumentAsPayload", method: "PUT") +operation InlineDocumentAsPayload { + input: InlineDocumentAsPayloadInputOutput, + output: InlineDocumentAsPayloadInputOutput +} + +structure InlineDocumentAsPayloadInputOutput { + @httpPayload + documentValue: Document, +} + +apply InlineDocumentAsPayload @httpRequestTests([ + { + id: "InlineDocumentAsPayloadInput", + documentation: "Serializes an inline document as the target of the httpPayload trait.", + protocol: restJson1, + method: "PUT", + uri: "/InlineDocumentAsPayload", + body: """ + { + "foo": "bar" + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + documentValue: { + foo: "bar" + } + } + } +]) + +apply InlineDocumentAsPayload @httpResponseTests([ + { + id: "InlineDocumentAsPayloadInputOutput", + documentation: "Serializes an inline document as the target of the httpPayload trait.", + protocol: restJson1, + code: 200, + body: """ + { + "foo": "bar" + }""", + bodyMediaType: "application/json", + headers: {"Content-Type": "application/json"}, + params: { + documentValue: { + foo: "bar" + } + } + } +]) diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy index 8ddbb34d101..902c8adfff6 100644 --- a/smithy-aws-protocol-tests/model/restJson1/main.smithy +++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy @@ -56,5 +56,9 @@ service RestJson { JsonLists, JsonMaps, JsonBlobs, + + // Documents + InlineDocument, + InlineDocumentAsPayload, ] } 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 f1fe2f720bd..99a7594d994 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 @@ -56,6 +56,7 @@ "selector": "service" }, "smithy.api#protocolDefinition": { + "noInlineDocumentSupport": true, "traits": [ "smithy.api#httpError", "smithy.api#httpHeader", @@ -124,6 +125,7 @@ "selector": "service" }, "smithy.api#protocolDefinition": { + "noInlineDocumentSupport": true, "traits": [ "smithy.api#xmlAttribute", "smithy.api#xmlFlattened", @@ -142,6 +144,7 @@ "selector": "service" }, "smithy.api#protocolDefinition": { + "noInlineDocumentSupport": true, "traits": [ "aws.protocols#ec2QueryName", "smithy.api#xmlAttribute", diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.errors new file mode 100644 index 00000000000..26c4ebafb16 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.errors @@ -0,0 +1,2 @@ +[ERROR] smithy.example#InvalidExample: This service uses the `aws.protocols#awsQuery` protocol which does not support inline document types, but the following document types were found in the closure of the service: [smithy.example#InlineDocument | NoInlineDocumentSupport +[SUPPRESSED] smithy.example#InvalidExample: This shape applies a trait that is deprecated: aws.protocols#awsQuery | DeprecatedTrait diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy new file mode 100644 index 00000000000..2852cba7e2e --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/xml-protocols-do-not-support-documents.smithy @@ -0,0 +1,23 @@ +// awsQuery does not support inline documents. Because an inline document is +// used in the closure of InvalidExample, this model creates a DANGER event. + +namespace smithy.example + +use aws.protocols#awsQuery + +@awsQuery +@suppress(["DeprecatedTrait"]) // ignore the fact that the awsQuery trait is deprecated +service InvalidExample { + version: "2020-06-15", + operations: [Operation1] +} + +operation Operation1 { + input: Operation1Input, +} + +structure Operation1Input { + foo: InlineDocument, +} + +document InlineDocument diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/ProtocolDefinitionTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/ProtocolDefinitionTrait.java index 457b2a0af04..89e546a2dcd 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/ProtocolDefinitionTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/ProtocolDefinitionTrait.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * 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. @@ -30,11 +30,16 @@ public final class ProtocolDefinitionTrait extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("smithy.api#protocolDefinition"); + private static final String NO_INLINE_DOCUMENT_SUPPORT = "noInlineDocumentSupport"; + private static final String TRAITS = "traits"; + private final List traits; + private final boolean noInlineDocumentSupport; public ProtocolDefinitionTrait(Builder builder) { super(ID, builder.getSourceLocation()); traits = ListUtils.copyOf(builder.traits); + noInlineDocumentSupport = builder.noInlineDocumentSupport; } /** @@ -47,6 +52,15 @@ public List getTraits() { return traits; } + /** + * Checks if this protocol does not support inline documents. + * + * @return Returns true if inline documents are not supported. + */ + public boolean getNoInlineDocumentSupport() { + return noInlineDocumentSupport; + } + public static Builder builder() { return new Builder(); } @@ -57,17 +71,27 @@ protected Node createNode() { return Node.objectNode(); } + ObjectNode.Builder builder = Node.objectNodeBuilder(); + ArrayNode ids = traits.stream() .map(ShapeId::toString) .map(Node::from) .collect(ArrayNode.collect()); + builder.withMember(TRAITS, ids); + + if (noInlineDocumentSupport) { + builder.withMember(NO_INLINE_DOCUMENT_SUPPORT, true); + } - return Node.objectNode().withMember("traits", ids); + return builder.build(); } @Override public Builder toBuilder() { - return builder().sourceLocation(getSourceLocation()).traits(traits); + return builder() + .sourceLocation(getSourceLocation()) + .traits(traits) + .noInlineDocumentSupport(noInlineDocumentSupport); } public static final class Provider extends AbstractTrait.Provider { @@ -79,17 +103,19 @@ public Provider() { public ProtocolDefinitionTrait 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)) { + objectNode.getArrayMember(TRAITS).ifPresent(traits -> { + for (String string : Node.loadArrayOfString(TRAITS, traits)) { builder.addTrait(ShapeId.from(string)); } }); + builder.noInlineDocumentSupport(objectNode.getBooleanMemberOrDefault(NO_INLINE_DOCUMENT_SUPPORT)); return builder.build(); } } public static final class Builder extends AbstractTraitBuilder { private final List traits = new ArrayList<>(); + private boolean noInlineDocumentSupport; @Override public ProtocolDefinitionTrait build() { @@ -106,5 +132,10 @@ public Builder addTrait(ShapeId trait) { traits.add(trait); return this; } + + public Builder noInlineDocumentSupport(boolean noInlineDocumentSupport) { + this.noInlineDocumentSupport = noInlineDocumentSupport; + return this; + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/NoInlineDocumentSupportValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/NoInlineDocumentSupportValidator.java new file mode 100644 index 00000000000..b7ac4a6c08e --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/NoInlineDocumentSupportValidator.java @@ -0,0 +1,104 @@ +/* + * 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.model.validation.validators; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeSet; +import java.util.stream.Collectors; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.neighbor.Walker; +import software.amazon.smithy.model.shapes.DocumentShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.ProtocolDefinitionTrait; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; + +/** + * Detects when a protocol indicates that it does not support inline documents, + * yet the protocol trait is attached to a service that uses inline documents. + */ +public final class NoInlineDocumentSupportValidator extends AbstractValidator { + @Override + public List validate(Model model) { + Set documents = model.shapes(DocumentShape.class).collect(Collectors.toSet()); + if (documents.isEmpty()) { + return Collections.emptyList(); + } + + Set noInlineDocumentSupport = findProtocolsWithNoInlineDocumentSupport(model); + if (noInlineDocumentSupport.isEmpty()) { + return Collections.emptyList(); + } + + List services = model.shapes(ServiceShape.class).collect(Collectors.toList()); + Walker walker = new Walker(model); + List events = new ArrayList<>(); + + for (ServiceShape service : services) { + // Find all service shapes that use a protocol that does not + // support documents. + for (ShapeId protocol :noInlineDocumentSupport) { + if (service.findTrait(protocol).isPresent()) { + // Find if the service uses a document. + Set shapes = walker.walkShapes(service); + Set foundDocuments = new TreeSet<>(); + for (DocumentShape documentShape : documents) { + if (shapes.contains(documentShape)) { + foundDocuments.add(documentShape); + } + } + // Only emit if a document was found in the closure. + if (!foundDocuments.isEmpty()) { + events.add(createEvent(service, service.findTrait(protocol).get(), foundDocuments)); + } + } + } + } + + return events; + } + + private Set findProtocolsWithNoInlineDocumentSupport(Model model) { + Set noInlineDocumentSupport = new HashSet<>(); + for (Shape shape : model.getShapesWithTrait(ProtocolDefinitionTrait.class)) { + ProtocolDefinitionTrait definitionTrait = shape.expectTrait(ProtocolDefinitionTrait.class); + if (definitionTrait.getNoInlineDocumentSupport()) { + noInlineDocumentSupport.add(shape.getId()); + } + } + return noInlineDocumentSupport; + } + + private ValidationEvent createEvent(ServiceShape service, Trait protocol, Set foundDocuments) { + StringJoiner joiner = new StringJoiner(", ", "[", "]"); + for (Shape document : foundDocuments) { + joiner.add(document.getId() + " @ " + document.getSourceLocation()); + } + + return error(service, protocol, String.format( + "This service uses the `%s` protocol which does not support inline document types, " + + "but the following document types were found in the closure of the service: %s", + protocol.toShapeId(), joiner.toString())); + } +} diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator index b48addc4a25..26a3c2f3b73 100644 --- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator +++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -15,6 +15,7 @@ software.amazon.smithy.model.validation.validators.HttpQueryTraitValidator software.amazon.smithy.model.validation.validators.HttpResponseCodeSemanticsValidator software.amazon.smithy.model.validation.validators.HttpUriConflictValidator software.amazon.smithy.model.validation.validators.LengthTraitValidator +software.amazon.smithy.model.validation.validators.NoInlineDocumentSupportValidator software.amazon.smithy.model.validation.validators.PaginatedTraitValidator software.amazon.smithy.model.validation.validators.PrivateAccessValidator software.amazon.smithy.model.validation.validators.RangeTraitValidator 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 8f2b5882103..592347f5304 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 @@ -85,6 +85,9 @@ structure protocolDefinition { /// Defines a list of traits that protocol implementations must /// understand in order to successfully use the protocol. traits: TraitShapeIdList, + + /// Set to true if inline documents are not supported by this protocol. + noInlineDocumentSupport: PrimitiveBoolean, } @private @@ -476,7 +479,10 @@ structure http { structure httpLabel {} /// Binds an operation input structure member to a query string parameter. -@trait(selector: "structure > :test(member > :test(simpleType, collection > member > simpleType))", +@trait(selector: """ + structure > member + :test(> simpleType:not(document), + > collection > member > simpleType:not(document)))""", conflicts: [httpLabel, httpHeader, httpPrefixHeaders, httpPayload]) @length(min: 1) @tags(["diff.error.const"]) @@ -494,7 +500,9 @@ string httpHeader /// Binds a map of key-value pairs to prefixed HTTP headers. @trait(selector: """ structure > member - :test(> map > member[id|member=value] > :test(simpleType, collection > member > simpleType))""", + :test(> map > member[id|member=value] > :test( + boolean, number, string, timestamp, + collection > member > :test(boolean, number, string, timestamp)))""", structurallyExclusive: "member", conflicts: [httpLabel, httpQuery, httpHeader, httpPayload]) @tags(["diff.error.const"]) diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.errors new file mode 100644 index 00000000000..c8c78ad2084 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.errors @@ -0,0 +1 @@ +[ERROR] smithy.example#FooService: This service uses the `smithy.example#noDocuments` protocol which does not support inline document types, but the following document types were found in the closure of the service: [smithy.example#InlineDocument | NoInlineDocumentSupport diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy new file mode 100644 index 00000000000..973240013e5 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/no-inline-document-support.smithy @@ -0,0 +1,32 @@ +namespace smithy.example + +@trait +@protocolDefinition(noInlineDocumentSupport: true) +structure noDocuments {} + +@trait +@protocolDefinition +structure yesDocuments {} + +@noDocuments +@yesDocuments +service FooService { + version: "2012-06-11", + operations: [Foo] +} + +operation Foo { + input: FooInput +} + +structure FooInput { + doc: InlineDocument, +} + +document InlineDocument + +@yesDocuments +service ValidService { + version: "2012-06-11", + operations: [Foo] +}