From ae4ca83447c8bb091db589a928d4919d6d40d730 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Sat, 21 Sep 2019 23:46:48 -0700 Subject: [PATCH] Refactor event streams to target members This commit removes the inputEventStream and outputEventStream traits and replaces them with the eventStream trait that targets members. This simplifies the model, validation, code generation, and I found that it makes it easier to model operations correctly. In fact, several typos were fixed in the documentation while making these changes due to the fact that the previous design required that the member name of an input/output shape is referenced from the operation. The major drawbacks of this approach are: 1) It's a breaking change. While this is a breaking change, no models are currently using the event stream feature in Smithy, and external tooling hasn't been published that consumes them either. 2) Structures that contain an event stream cannot be targeted by anything other than operation input or output. While it's awkward that event stream structures are essentially "poisoned" and can't be targeted by other members or used as errors, it does make event stream far easier to use with code generation since the structure itself does not need to be specialized when generating operation inputs and outputs. If we find ourselves needing to use this for other abstractions, something more reusable like a trait attribute could be introduced to automate this validation. --- docs/source/spec/event-streams.rst | 155 ++++++++---------- docs/source/spec/http.rst | 24 +-- docs/source/spec/mqtt.rst | 32 ++-- .../model/knowledge/EventStreamIndex.java | 69 ++++---- .../model/knowledge/EventStreamInfo.java | 8 +- .../amazon/smithy/model/loader/Prelude.java | 6 +- ...StreamTrait.java => EventStreamTrait.java} | 19 +-- .../model/traits/OutputEventStreamTrait.java | 40 ----- .../validators/EventStreamValidator.java | 136 +++++++-------- ...re.amazon.smithy.model.traits.TraitService | 3 +- .../smithy/model/loader/prelude-traits.smithy | 16 +- .../model/knowledge/EventStreamIndexTest.java | 22 ++- .../validators/event-stream-validator.errors | 11 +- .../validators/event-stream-validator.json | 75 +++------ .../model/knowledge/event-stream-index.json | 20 ++- .../validators/MqttPublishInputValidator.java | 53 ++++++ ...e.amazon.smithy.model.validation.Validator | 1 + .../META-INF/smithy/smithy.api.mqtt.smithy | 4 +- .../testsuite/dropped-mqtt-headers.smithy | 2 +- .../mqtt/traits/testsuite/job-service.smithy | 17 +- .../mqtt-operations-with-errors.smithy | 2 +- .../testsuite/publish-with-eventstream.errors | 2 +- .../testsuite/publish-with-eventstream.smithy | 2 +- .../subscribe-input-missing-label.smithy | 2 +- .../testsuite/subscribe-missing-output.errors | 2 +- .../subscribe-operation-initial-event.smithy | 2 +- ...ribe-operation-missing-event-stream.errors | 2 +- .../traits/testsuite/topic-conflicts.smithy | 4 +- .../fromsmithy/mappers/UnsupportedTraits.java | 2 +- 29 files changed, 352 insertions(+), 381 deletions(-) rename smithy-model/src/main/java/software/amazon/smithy/model/traits/{InputEventStreamTrait.java => EventStreamTrait.java} (58%) delete mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputEventStreamTrait.java create mode 100644 smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java diff --git a/docs/source/spec/event-streams.rst b/docs/source/spec/event-streams.rst index 68d1644c487..1f4e29ff0d0 100644 --- a/docs/source/spec/event-streams.rst +++ b/docs/source/spec/event-streams.rst @@ -4,17 +4,16 @@ Event stream specification ========================== -An event stream is an abstraction that allows messages to be sent -asynchronously over a persistent connection between a client and server. -Event streams support both duplex and simplex streaming. The serialization -format and framing of messages sent over event streams is defined by the -:ref:`protocol ` of a service. - -Event streams are defined on operations using the -:ref:`inputEventStream-trait` and :ref:`outputEventStream-trait`. These traits -reference a member of the input or output of an operation that targets the -shape sent over the event stream. A member that targets a structure is a -*single-event event stream*, and a member that targets a union is a +An event stream is an abstraction that allows multiple messages to be sent +asynchronously between a client and server. Event streams support both duplex +and simplex streaming. The serialization format and framing of messages sent +over event streams is defined by the :ref:`protocol ` of a +service. + +An operation can send an event stream as part of its input or output. An +event stream is formed when an input or output member of an operation is +marked with the :ref:`eventStream-trait`. A member that targets a structure +is a *single-event event stream*, and a member that targets a union is a *multi-event event stream*. .. contents:: Table of contents @@ -30,14 +29,12 @@ Single-event event streams -------------------------- A *single-event event stream* is an event stream that streams zero or more -instances of a specific structure shape. It is formed when the -``inputEventStream`` or ``outputEventStream`` trait references an input or -output member that targets a structure. +instances of a specific structure shape. .. _single-event-input-eventstream: The following example defines an operation that uses a single-event event -stream in its input by referencing a member that targets a structure: +stream in its input: .. tabs:: @@ -45,11 +42,12 @@ stream in its input by referencing a member that targets a structure: namespace smithy.example - @inputEventStream("messages") operation PublishMessages(PublishMessagesInput) structure PublishMessagesInput { room: String, + + @eventStream messages: Message, } @@ -65,8 +63,7 @@ stream in its input by referencing a member that targets a structure: "shapes": { "PublishMessages": { "type": "operation", - "input": "PublishMessagesInput", - "inputEventStream": "messages" + "input": "PublishMessagesInput" }, "PublishMessagesInput": { "type": "structure", @@ -75,7 +72,8 @@ stream in its input by referencing a member that targets a structure: "target": "String" }, "messages": { - "target": "Message" + "target": "Message", + "eventStream": true } } }, @@ -102,10 +100,10 @@ stream in its output: namespace smithy.example - @outputEventStream("movements") operation SubscribeToMovements() -> SubscribeToMovementsOutput structure SubscribeToMovementsOutput { + @eventStream movements: Movement, } @@ -122,14 +120,14 @@ stream in its output: "shapes": { "SubscribeToMovements": { "type": "operation", - "output": "SubscribeToMovementsOutput", - "outputEventStream": "movements" + "output": "SubscribeToMovementsOutput" }, "SubscribeToMovementsOutput": { "type": "structure", "members": { "movements": { - "target": "Movement" + "target": "Movement", + "eventStream": true } } }, @@ -148,6 +146,9 @@ stream in its output: } } +The name of the event sent over a single-event event stream is the name +of the member that is targeted by the ``eventStream`` trait. + Single-event client behavior ============================ @@ -168,12 +169,11 @@ Multi-event event streams ------------------------- A *multi-event event stream* is an event stream that streams any number of -named event structure shapes defined by a union. It is formed when an -``inputEventStream`` or ``outputEventStream`` trait references an input or -output member that targets a union. Each member of the targeted union MUST -target a structure shape. The member names of the -union define the name that is used to identify each event that is sent over -the event stream. +named event structure shapes defined by a union. It is formed when the +``eventStream`` trait is applied to a member that targets a union. Each +member of the targeted union MUST target a structure shape. The member +names of the union define the name that is used to identify each event +that is sent over the event stream. .. _multi-event-input-eventstream: @@ -186,11 +186,12 @@ stream in its input by referencing a member that targets a union: namespace smithy.example - @inputEventStream("messages") operation PublishMessages(PublishMessagesInput) structure PublishMessagesInput { room: String, + + @eventStream messages: PublishEvents, } @@ -213,8 +214,7 @@ stream in its input by referencing a member that targets a union: "shapes": { "PublishMessages": { "type": "operation", - "input": "PublishMessagesInput", - "inputEventStream": "messages" + "input": "PublishMessagesInput" }, "PublishMessagesInput": { "type": "structure", @@ -223,7 +223,8 @@ stream in its input by referencing a member that targets a union: "target": "String" }, "messages": { - "target": "PublishEvents" + "target": "PublishEvents", + "eventStream": true } } }, @@ -257,10 +258,10 @@ stream in its output: namespace smithy.example - @outputEventStream("movements") operation SubscribeToMovements() -> SubscribeToMovementsOutput structure SubscribeToMovementsOutput { + @eventStream movements: MovementEvents, } @@ -283,14 +284,14 @@ stream in its output: "shapes": { "SubscribeToMovements": { "type": "operation", - "output": "SubscribeToMovementsOutput", - "outputEventStream": "movements" + "output": "SubscribeToMovementsOutput" }, "SubscribeToMovementsOutput": { "type": "structure", "members": { "movements": { - "target": "Message" + "target": "Message", + "eventStream": true } } }, @@ -342,10 +343,10 @@ on the name of an event. For example, given the following event stream, namespace smithy.example - @outputEventStream("movements") operation SubscribeToEvents() -> SubscribeToEventsOutput structure SubscribeToEventsOutput { + @eventStream events: Events, } @@ -372,10 +373,9 @@ Initial messages ---------------- An *initial message* is comprised of the top-level input or output members -of an operation that are not referenced by the ``inputEventStream`` or -``outputEventStream`` traits. Initial messages provide an opportunity for a -client or server to provide metadata about an event stream before transmitting -events. +of an operation that are not targeted by the ``eventStream`` trait. Initial +messages provide an opportunity for a client or server to provide metadata +about an event stream before transmitting events. .. important:: @@ -389,11 +389,10 @@ Initial-request =============== An *initial-request* is an initial message that can be sent from a client to -a server for an operation marked with the ``inputEventStream`` trait. The -structure of an initial-request is the input of an operation with no value -provided for the event stream member referenced by the ``inputEventStream`` -trait. An initial-request, if sent, is sent from a client to a server before -sending any event stream events. +a server for an operation with an input event stream. The structure of an +initial-request is the input of an operation with no value provided for the +event stream member. An initial-request, if sent, is sent from a client to a +server before sending any event stream events. When using :ref:`HTTP bindings `, initial-request fields are mapped to specific locations in the HTTP request such as headers or the @@ -410,7 +409,6 @@ service, followed by the events sent in the payload of the HTTP message. namespace smithy.example - @inputEventStream("messages") @http(method: "POST", uri: "/messages/{room}") operation PublishMessages(PublishMessagesInput) @@ -419,6 +417,7 @@ service, followed by the events sent in the payload of the HTTP message. room: String, @httpPayload + @eventStream messages: Message, } @@ -435,7 +434,6 @@ service, followed by the events sent in the payload of the HTTP message. "PublishMessages": { "type": "operation", "input": "PublishMessagesInput", - "inputEventStream": "messages", "http": { "uri": "/messages/{room}", "method": "POST" @@ -450,7 +448,8 @@ service, followed by the events sent in the payload of the HTTP message. }, "messages": { "target": "Message", - "httpPayload": true + "httpPayload": true, + "eventStream": true } } }, @@ -473,10 +472,9 @@ Initial-response ================ An *initial-response* is an initial message that can be sent from a server -to a client for an operation marked with the ``outputEventStream`` trait. -The structure of an initial-response is the output of an operation with no -value provided for the event stream member referenced by the -``outputEventStream`` trait. An initial-response, if sent, is sent from the +to a client for an operation with an output event stream. The structure of +an initial-response is the output of an operation with no value provided for +the event stream member. An initial-response, if sent, is sent from the server to the client before sending any event stream events. When using :ref:`HTTP bindings `, initial-response fields are @@ -494,7 +492,6 @@ message. namespace smithy.example - @outputEventStream("messages") @http(method: "GET", uri: "/messages/{room}") operation SubscribeToMessages(SubscribeToMessagesInput) -> SubscribeToMessagesOutput @@ -508,6 +505,7 @@ message. connectionLifetime: Integer, @httpPayload + @eventStream messages: Message, } @@ -520,7 +518,6 @@ message. "PublishMessages": { "type": "operation", "input": "PublishMessagesInput", - "inputEventStream": "messages", "http": { "uri": "/messages/{room}", "method": "POST" @@ -544,7 +541,8 @@ message. }, "messages": { "target": "Message", - "httpPayload": true + "httpPayload": true, + "eventStream": true } } } @@ -665,46 +663,33 @@ based protocol, the event payload is serialized as a JSON object: Event stream traits ------------------- -.. _inputEventstream-trait: +.. _eventStream-trait: -``inputEventStream`` trait +``eventStream`` trait ========================== Summary - Configures an operation to use an event stream for its input. + Configures a member of an operation input or output as an event stream. Trait selector - ``operation:test(-[input]->)`` + ``operation -[input, output]-> structure > :test(member > :each(structure, union))`` - An operation that defines an input shape. + An operation input or output member that targets a structure or union. Value type - String value that is the name of a member of the operation input - structure. The referenced member MUST exist on the input structure - of the operation, MUST NOT be marked as required, and MUST target a - structure or a union that targets only structure shapes. + Annotation trait. +Conflicts with + :ref:`required-trait` Examples * :ref:`Single-event event stream example ` * :ref:`Multi-event event stream example ` +A structure that contains a member marked with the ``eventStream`` trait +can only be referenced by operation input or output shapes. Structures +that contain an event stream cannot be referenced by members or used as +part of an :ref:`error `. -.. _outputEventStream-trait: - -``outputEventStream`` trait -=========================== - -Summary - Configures an operation to use an event stream for its output. -Trait selector - ``operation:test(-[output]->)`` - - An operation that defines an output shape. -Value type - String value that is the name of a member of the operation output - structure. The referenced member MUST exist on the output structure - of the operation, MUST NOT be marked as required, and MUST target - a structure or a union that targets only structure shapes. -Examples - * :ref:`Single-event event stream example ` - * :ref:`Multi-event event stream example ` +The member targeted by the ``eventStream`` trait MUST NOT be marked as +required because the input or output structure also functions as an +initial-message. .. _eventheader-trait: diff --git a/docs/source/spec/http.rst b/docs/source/spec/http.rst index 1be5bd11d92..9b4fde6381c 100644 --- a/docs/source/spec/http.rst +++ b/docs/source/spec/http.rst @@ -921,11 +921,10 @@ Event streams When using :ref:`event streams ` and HTTP bindings, the :ref:`httpPayload ` trait MUST be applied to any input or -output member referenced by the :ref:`inputeventstream-trait` and -:ref:`outputeventstream-trait` of an operation. +output member targeted by the :ref:`eventStream-trait`. -The following example defines an operation that uses an ``inputEventStream`` and -HTTP bindings: +The following example defines an operation that uses an input event stream +and HTTP bindings: .. tabs:: @@ -933,12 +932,12 @@ HTTP bindings: namespace smithy.example - @inputEventStream("messages") @http(method: "POST", uri: "/messages") operation PublishMessages(PublishMessagesInput) structure PublishMessagesInput { @httpPayload + @eventStream messages: Message, } @@ -955,13 +954,16 @@ HTTP bindings: "PublishMessages": { "type": "operation", "input": "PublishMessagesInput", - "inputEventStream": "messages", "http": { "uri": "/messages", "method": "POST" } }, "PublishMessagesInput": { "type": "structure", "members": { - "messages": { "target": "Message", "httpPayload": true } + "messages": { + "target": "Message", + "httpPayload": true, + "eventStream": true + } } }, "Message": { @@ -974,19 +976,19 @@ HTTP bindings: } } -The following is **invalid** because the operation has the ``http`` trait, -it has the ``inputEventStream`` trait, and the ``inputEventStream`` trait -references a member that is not marked with the ``httpPayload`` trait: +The following is **invalid** because the operation has the ``http`` trait +and an input member is marked with the ``eventStream`` trait but not +marked with the ``httpPayload`` trait: .. code-block:: smithy namespace smithy.example - @inputEventStream("invalid") @http(method: "POST", uri: "/messages") operation InvalidOperation(InvalidOperationInput) structure InvalidOperationInput { + @eventStream invalid: Message, // <-- Missing the @httpPayload trait } diff --git a/docs/source/spec/mqtt.rst b/docs/source/spec/mqtt.rst index 0dbb37ca426..25c595f4c87 100644 --- a/docs/source/spec/mqtt.rst +++ b/docs/source/spec/mqtt.rst @@ -181,7 +181,7 @@ Trait value reference top-level input members of the operation by case-sensitive member name. Conflicts with - :ref:`smithy.mqtt#subscribe-trait`, :ref:`inputEventStream-trait` + :ref:`smithy.mqtt#subscribe-trait` Input members that are not marked with the :ref:`smithy.mqtt#topicLabel-trait` come together to form the protocol-specific payload of the PUBLISH message. @@ -268,9 +268,9 @@ Trait summary Binds an operation to send one or more SUBSCRIBE control packets via the MQTT protocol. Trait selector - ``operation[trait|outputEventStream]`` + ``operation:test(-[output]-> structure -> member[trait|eventStream])`` - *An operation with an outputEventStream trait* + *An operation with an output event stream* Trait value ``string`` value that is a valid :ref:`MQTT topic template `. The MQTT topic template MAY contain label placeholders that reference @@ -282,12 +282,13 @@ No message is published when using an operation marked with the ``smithy.mqtt#subscribe`` trait. All members of the input of the operation MUST be marked with valid ``smithy.mqtt#topicLabel`` traits. -The operation MUST have an :ref:`outputEventStream-trait`. The top-level -output member referenced by this trait represents the message that is sent -over the MQTT topic. An abstraction for automatically subscribing to and -asynchronously receiving events SHOULD be provided by Smithy clients. When -that abstraction is destroyed, the client SHOULD provide the ability to -automatically UNSUBSCRIBE from topics. +The operation MUST define an output shape that has an +:ref:`eventStream `. The top-level output member referenced +by this trait represents the message(s) sent over the MQTT topic. An +abstraction for automatically subscribing to and asynchronously receiving +events SHOULD be provided by Smithy clients. When that abstraction is +destroyed, the client SHOULD provide the ability to automatically UNSUBSCRIBE +from topics. .. important:: @@ -306,7 +307,6 @@ topic using a :ref:`single-event event stream `: use smithy.mqtt#topicLabel @subscribe("events/{id}") - @outputEventStream("events") operation SubscribeForEvents(SubscribeForEventsInput) -> SubscribeForEventsOutput structure SubscribeForEventsInput { @@ -316,6 +316,7 @@ topic using a :ref:`single-event event stream `: } structure SubscribeForEventsOutput { + @eventStream events: Event, } @@ -332,8 +333,7 @@ topic using a :ref:`single-event event stream `: "SubscribeForEvents": { "type": "operation", "input": "SubscribeForEventsInput", - "smithy.mqtt#subscribe": "events/{id}", - "outputEventStream": "events" + "smithy.mqtt#subscribe": "events/{id}" }, "SubscribeForEventsInput": { "type": "structure", @@ -349,7 +349,8 @@ topic using a :ref:`single-event event stream `: "type": "structure", "members": { "events": { - "target": "Event" + "target": "Event", + "eventStream": true } } }, @@ -489,13 +490,13 @@ MQTT protocol bindings. namespace smithy.mqtt @trait(selector: "operation:not(-[output]->)", - conflicts: ["smithy.mqtt#subscribe", "inputEventStream"]) + conflicts: ["smithy.mqtt#subscribe"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". @pattern("^[^#+]+$") string publish - @trait(selector: "operation[trait|outputEventStream]", + @trait(selector: "operation:test(-[output]-> structure > member[trait|eventStream])", conflicts: ["smithy.mqtt#publish"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". @@ -506,6 +507,5 @@ MQTT protocol bindings. structure topicLabel {} - .. _MQTT PUBLISH: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037 .. _MQTT topic level: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106 diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java index cbdb6a9dc31..96c33eb24d7 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamIndex.java @@ -27,9 +27,7 @@ import software.amazon.smithy.model.shapes.ShapeIndex; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; -import software.amazon.smithy.model.traits.InputEventStreamTrait; -import software.amazon.smithy.model.traits.OutputEventStreamTrait; -import software.amazon.smithy.model.traits.StringTrait; +import software.amazon.smithy.model.traits.EventStreamTrait; /** * Index of operation shapes to event stream information. @@ -50,19 +48,30 @@ public EventStreamIndex(Model model) { OperationIndex operationIndex = model.getKnowledge(OperationIndex.class); model.getShapeIndex().shapes(OperationShape.class).forEach(operation -> { - operation.getTrait(InputEventStreamTrait.class).ifPresent(trait -> { - operationIndex.getInput(operation) - .flatMap(input -> createEventStreamInfo(index, operation, input, trait)) - .ifPresent(info -> inputInfo.put(operation.getId(), info)); + operationIndex.getInput(operation).ifPresent(input -> { + computeEvents(index, operation, input, inputInfo); }); - operation.getTrait(OutputEventStreamTrait.class).ifPresent(trait -> { - operationIndex.getOutput(operation) - .flatMap(output -> createEventStreamInfo(index, operation, output, trait)) - .ifPresent(info -> outputInfo.put(operation.getId(), info)); + operationIndex.getOutput(operation).ifPresent(output -> { + computeEvents(index, operation, output, outputInfo); }); }); } + private void computeEvents( + ShapeIndex index, + OperationShape operation, + StructureShape shape, + Map infoMap + ) { + for (MemberShape member : shape.getAllMembers().values()) { + if (member.hasTrait(EventStreamTrait.class)) { + createEventStreamInfo(index, operation, shape, member).ifPresent(info -> { + infoMap.put(operation.getId(), info); + }); + } + } + } + /** * Get event stream information for the input of an operation. * @@ -97,59 +106,51 @@ private Optional createEventStreamInfo( ShapeIndex index, OperationShape operation, StructureShape structure, - StringTrait trait + MemberShape member ) { - String eventStreamMemberName = trait.getValue(); - - MemberShape eventStreamMember = structure.getMember(eventStreamMemberName).orElse(null); - if (eventStreamMember == null) { - LOGGER.severe(() -> String.format( - "Skipping event stream info for %s because the member %s does not exist", - operation.getId(), eventStreamMemberName)); - return Optional.empty(); - } + EventStreamTrait trait = member.getTrait(EventStreamTrait.class).get(); - Shape eventStreamTarget = index.getShape(eventStreamMember.getTarget()).orElse(null); + Shape eventStreamTarget = index.getShape(member.getTarget()).orElse(null); if (eventStreamTarget == null) { LOGGER.severe(String.format( "Skipping event stream info for %s because the %s member target %s does not exist", - operation.getId(), eventStreamMemberName, eventStreamMember.getTarget())); + operation.getId(), member.getMemberName(), member.getTarget())); return Optional.empty(); } // Compute the events of the event stream. Map events = new HashMap<>(); if (eventStreamTarget.asUnionShape().isPresent()) { - for (MemberShape member : eventStreamTarget.asUnionShape().get().getAllMembers().values()) { - index.getShape(member.getTarget()).flatMap(Shape::asStructureShape).ifPresent(struct -> { - events.put(member.getMemberName(), struct); + for (MemberShape unionMember : eventStreamTarget.asUnionShape().get().getAllMembers().values()) { + index.getShape(unionMember.getTarget()).flatMap(Shape::asStructureShape).ifPresent(struct -> { + events.put(unionMember.getMemberName(), struct); }); } } else if (eventStreamTarget.asStructureShape().isPresent()) { - events.put(trait.getValue(), eventStreamTarget.asStructureShape().get()); + events.put(member.getMemberName(), eventStreamTarget.asStructureShape().get()); } else { // If the event target is an invalid type, then we can't create the indexed result. LOGGER.severe(() -> String.format( "Skipping event stream info for %s because the %s member target %s is not a structure or union", - operation.getId(), eventStreamMemberName, eventStreamMember.getTarget())); + operation.getId(), member.getMemberName(), member.getTarget())); return Optional.empty(); } Map initialMembers = new HashMap<>(); Map initialTargets = new HashMap<>(); - for (MemberShape member : structure.getAllMembers().values()) { - if (!member.getMemberName().equals(eventStreamMemberName)) { - index.getShape(member.getTarget()).ifPresent(shapeTarget -> { - initialMembers.put(member.getMemberName(), member); - initialTargets.put(member.getMemberName(), shapeTarget); + for (MemberShape structureMember : structure.getAllMembers().values()) { + if (!structureMember.getMemberName().equals(member.getMemberName())) { + index.getShape(structureMember.getTarget()).ifPresent(shapeTarget -> { + initialMembers.put(structureMember.getMemberName(), structureMember); + initialTargets.put(structureMember.getMemberName(), shapeTarget); }); } } return Optional.of(new EventStreamInfo( operation, trait, structure, - eventStreamMember, eventStreamTarget, + member, eventStreamTarget, initialMembers, initialTargets, events)); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamInfo.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamInfo.java index e9fa1bdca49..7b7acd9ca5e 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamInfo.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/EventStreamInfo.java @@ -22,7 +22,7 @@ import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.traits.StringTrait; +import software.amazon.smithy.model.traits.EventStreamTrait; /** * Contains extracted event stream information. @@ -35,11 +35,11 @@ public final class EventStreamInfo { private final Map initialMembers; private final Map initialTargets; private final Map events; - private final StringTrait eventStreamTrait; + private final EventStreamTrait eventStreamTrait; EventStreamInfo( OperationShape operation, - StringTrait eventStreamTrait, + EventStreamTrait eventStreamTrait, StructureShape structure, MemberShape eventStreamMember, Shape eventStreamTarget, @@ -71,7 +71,7 @@ public OperationShape getOperation() { * * @return Returns the event stream trait. */ - public StringTrait getEventStreamTrait() { + public EventStreamTrait getEventStreamTrait() { return eventStreamTrait; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/Prelude.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/Prelude.java index de708b19c0f..1187482c3da 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/Prelude.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/Prelude.java @@ -53,6 +53,7 @@ import software.amazon.smithy.model.traits.ErrorTrait; import software.amazon.smithy.model.traits.EventHeaderTrait; import software.amazon.smithy.model.traits.EventPayloadTrait; +import software.amazon.smithy.model.traits.EventStreamTrait; import software.amazon.smithy.model.traits.ExamplesTrait; import software.amazon.smithy.model.traits.ExternalDocumentationTrait; import software.amazon.smithy.model.traits.HostLabelTrait; @@ -66,11 +67,9 @@ import software.amazon.smithy.model.traits.IdRefTrait; import software.amazon.smithy.model.traits.IdempotencyTokenTrait; import software.amazon.smithy.model.traits.IdempotentTrait; -import software.amazon.smithy.model.traits.InputEventStreamTrait; import software.amazon.smithy.model.traits.JsonNameTrait; import software.amazon.smithy.model.traits.LengthTrait; import software.amazon.smithy.model.traits.MediaTypeTrait; -import software.amazon.smithy.model.traits.OutputEventStreamTrait; import software.amazon.smithy.model.traits.PaginatedTrait; import software.amazon.smithy.model.traits.PatternTrait; import software.amazon.smithy.model.traits.PrivateTrait; @@ -156,6 +155,7 @@ public final class Prelude { ErrorTrait.ID, EventHeaderTrait.ID, EventPayloadTrait.ID, + EventStreamTrait.ID, ExamplesTrait.ID, ExternalDocumentationTrait.ID, HostLabelTrait.ID, @@ -169,11 +169,9 @@ public final class Prelude { IdRefTrait.ID, IdempotencyTokenTrait.ID, IdempotentTrait.ID, - InputEventStreamTrait.ID, JsonNameTrait.ID, LengthTrait.ID, MediaTypeTrait.ID, - OutputEventStreamTrait.ID, PaginatedTrait.ID, PatternTrait.ID, PrivateTrait.ID, diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/InputEventStreamTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/EventStreamTrait.java similarity index 58% rename from smithy-model/src/main/java/software/amazon/smithy/model/traits/InputEventStreamTrait.java rename to smithy-model/src/main/java/software/amazon/smithy/model/traits/EventStreamTrait.java index 3e0c44c7096..f4755fdee76 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/InputEventStreamTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/EventStreamTrait.java @@ -18,23 +18,20 @@ import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.shapes.ShapeId; -/** - * Trait implementation of inputEventStream. - */ -public final class InputEventStreamTrait extends StringTrait { - public static final ShapeId ID = ShapeId.from("smithy.api#inputEventStream"); +public final class EventStreamTrait extends BooleanTrait { + public static final ShapeId ID = ShapeId.from("smithy.api#eventStream"); - public InputEventStreamTrait(String value, SourceLocation sourceLocation) { - super(ID, value, sourceLocation); + public EventStreamTrait(SourceLocation sourceLocation) { + super(ID, sourceLocation); } - public InputEventStreamTrait(String value) { - this(value, SourceLocation.NONE); + public EventStreamTrait() { + this(SourceLocation.NONE); } - public static final class Provider extends StringTrait.Provider { + public static final class Provider extends BooleanTrait.Provider { public Provider() { - super(ID, InputEventStreamTrait::new); + super(ID, EventStreamTrait::new); } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputEventStreamTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputEventStreamTrait.java deleted file mode 100644 index cba0943c8e0..00000000000 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/OutputEventStreamTrait.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019 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.traits; - -import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.model.shapes.ShapeId; - -/** - * Trait implementation of outputEventStream. - */ -public final class OutputEventStreamTrait extends StringTrait { - public static final ShapeId ID = ShapeId.from("smithy.api#outputEventStream"); - - public OutputEventStreamTrait(String value, SourceLocation sourceLocation) { - super(ID, value, sourceLocation); - } - - public OutputEventStreamTrait(String value) { - this(value, SourceLocation.NONE); - } - - public static final class Provider extends StringTrait.Provider { - public Provider() { - super(ID, OutputEventStreamTrait::new); - } - } -} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/EventStreamValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/EventStreamValidator.java index 855f63c375e..0cb2de54ead 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/EventStreamValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/EventStreamValidator.java @@ -16,18 +16,22 @@ package software.amazon.smithy.model.validation.validators; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.NeighborProviderIndex; +import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.neighbor.NeighborProvider; +import software.amazon.smithy.model.neighbor.Relationship; +import software.amazon.smithy.model.neighbor.RelationshipDirection; +import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeIndex; import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.traits.InputEventStreamTrait; -import software.amazon.smithy.model.traits.OutputEventStreamTrait; -import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.traits.EventStreamTrait; import software.amazon.smithy.model.validation.AbstractValidator; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.utils.ListUtils; @@ -37,35 +41,39 @@ * Validates inputEventStream and outputEventStream traits. * *
    - *
  • Ensures that event stream members are present.
  • - *
  • Ensures that event stream members are not required.
  • *
  • Ensures that event stream members target structures or unions.
  • *
  • Ensures that event stream union members are all structures.
  • + *
  • Ensures that structures with event streams are only targeted by input/output.
  • *
*/ public class EventStreamValidator extends AbstractValidator { @Override public List validate(Model model) { ShapeIndex index = model.getShapeIndex(); + OperationIndex operationIndex = model.getKnowledge(OperationIndex.class); List events = new ArrayList<>(); + List eventStreamStructures = new ArrayList<>(); - model.getShapeIndex().shapes(OperationShape.class) - .flatMap(operation -> Trait.flatMapStream(operation, InputEventStreamTrait.class)) - .forEach(pair -> { - OperationShape operation = pair.getLeft(); - ShapeId input = operation.getInput().orElse(null); - InputEventStreamTrait trait = pair.getRight(); - events.addAll(check(index, operation, input, trait, trait.getValue())); - }); + model.getShapeIndex().shapes(OperationShape.class).forEach(operation -> { + operationIndex.getInput(operation).ifPresent(input -> { + for (MemberShape member : input.getAllMembers().values()) { + if (member.hasTrait(EventStreamTrait.class)) { + eventStreamStructures.add(input); + events.addAll(check(index, operation, member, "input")); + } + } + }); + operationIndex.getOutput(operation).ifPresent(output -> { + for (MemberShape member : output.getAllMembers().values()) { + if (member.hasTrait(EventStreamTrait.class)) { + eventStreamStructures.add(output); + events.addAll(check(index, operation, member, "output")); + } + } + }); + }); - model.getShapeIndex().shapes(OperationShape.class) - .flatMap(operation -> Trait.flatMapStream(operation, OutputEventStreamTrait.class)) - .forEach(pair -> { - OperationShape operation = pair.getLeft(); - ShapeId input = operation.getOutput().orElse(null); - OutputEventStreamTrait trait = pair.getRight(); - events.addAll(check(index, operation, input, trait, trait.getValue())); - }); + events.addAll(validateEventStreamTargets(model, eventStreamStructures)); return events; } @@ -73,62 +81,19 @@ public List validate(Model model) { private List check( ShapeIndex index, OperationShape operation, - ShapeId inputOutput, - Trait trait, - String member - ) { - String inputOrOutputName = trait instanceof InputEventStreamTrait ? "input" : "output"; - - if (inputOutput == null) { - // The operation has no input/output, so this is the only check. - return ListUtils.of(error(operation, trait, String.format( - "Operation has the `%s` but does not define an %s structure.", - trait.toShapeId().getName(), inputOrOutputName))); - } - - StructureShape struct = index.getShape(inputOutput).flatMap(Shape::asStructureShape).orElse(null); - if (struct == null) { - // Broken targets are validated elsewhere. - return ListUtils.of(); - } - - MemberShape actualMember = struct.getMember(member).orElse(null); - if (actualMember == null) { - // Stop because member-specific validation can't be performed. - return ListUtils.of(error(operation, trait, String.format( - "Operation %s member `%s` was not found for the `%s` trait.", - inputOrOutputName, member, trait.toShapeId().getName()))); - } - - List events = new ArrayList<>(); - if (actualMember.isRequired()) { - events.add(error(operation, trait, String.format( - "Operation %s member `%s` is referenced by an `%s` trait, so it cannot be marked as required.", - inputOrOutputName, actualMember.getId(), trait.toShapeId().getName()))); - } - - // Additional event stream specific validation can't be performed, - // and broken member target validation is covered elsewhere. - index.getShape(actualMember.getTarget()).ifPresent(referencedMember -> { - events.addAll(checkReferencedMember( - index, operation, trait, actualMember, referencedMember, inputOrOutputName)); - }); - - return events; - } - - private List checkReferencedMember( - ShapeIndex index, - OperationShape operation, - Trait trait, MemberShape member, - Shape referencedMember, String inputOrOutputName ) { - if (referencedMember.asUnionShape().isPresent()) { + Shape target = index.getShape(member.getTarget()).orElse(null); + if (target == null) { + return Collections.emptyList(); + } + + EventStreamTrait trait = member.getTrait(EventStreamTrait.class).get(); + if (target.asUnionShape().isPresent()) { // Find members that don't reference a structure and combine // these member names into a comma separated list. - String invalidMembers = referencedMember.asUnionShape().get().getAllMembers().values().stream() + String invalidMembers = target.asUnionShape().get().getAllMembers().values().stream() .map(em -> Pair.of(em.getMemberName(), index.getShape(em.getTarget()).orElse(null))) .filter(pair -> pair.getRight() != null && !(pair.getRight() instanceof StructureShape)) .map(Pair::getLeft) @@ -138,16 +103,35 @@ private List checkReferencedMember( return ListUtils.of(error(operation, trait, String.format( "Operation %s member `%s` targets an invalid union `%s`; each member of an event " + "stream union must target a structure shape, but the following union members do not: [%s]", - inputOrOutputName, member.getId(), referencedMember.getId(), invalidMembers))); + inputOrOutputName, member.getId(), target.getId(), invalidMembers))); } - } else if (!referencedMember.isStructureShape()) { + } else if (!target.isStructureShape()) { return ListUtils.of(error(operation, trait, String.format( "Operation %s member `%s` is referenced by the `%s` trait, so it must target a structure " + "or union, but found %s, a %s.", inputOrOutputName, member.getId(), trait.toShapeId().getName(), - referencedMember.getId(), referencedMember.getType()))); + target.getId(), target.getType()))); } return ListUtils.of(); } + + private List validateEventStreamTargets(Model model, List shapes) { + List events = new ArrayList<>(); + NeighborProvider provider = model.getKnowledge(NeighborProviderIndex.class).getReverseProvider(); + + for (Shape shape : shapes) { + for (Relationship rel : provider.getNeighbors(shape)) { + if (rel.getRelationshipType() != RelationshipType.INPUT + && rel.getRelationshipType() != RelationshipType.OUTPUT + && rel.getRelationshipType().getDirection() == RelationshipDirection.DIRECTED) { + events.add(error(rel.getShape(), String.format( + "This shape has an invalid `%s` relationship to a structure, `%s`, that contains " + + "an event stream", rel.getRelationshipType(), shape.getId()))); + } + } + } + + return events; + } } diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService index 53cd3489031..a56a2c15b6e 100644 --- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService +++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -8,6 +8,7 @@ software.amazon.smithy.model.traits.EnumTrait$Provider software.amazon.smithy.model.traits.ErrorTrait$Provider software.amazon.smithy.model.traits.EventHeaderTrait$Provider software.amazon.smithy.model.traits.EventPayloadTrait$Provider +software.amazon.smithy.model.traits.EventStreamTrait$Provider software.amazon.smithy.model.traits.ExamplesTrait$Provider software.amazon.smithy.model.traits.ExternalDocumentationTrait$Provider software.amazon.smithy.model.traits.HostLabelTrait$Provider @@ -21,11 +22,9 @@ software.amazon.smithy.model.traits.HttpTrait$Provider software.amazon.smithy.model.traits.IdempotencyTokenTrait$Provider software.amazon.smithy.model.traits.IdempotentTrait$Provider software.amazon.smithy.model.traits.IdRefTrait$Provider -software.amazon.smithy.model.traits.InputEventStreamTrait$Provider software.amazon.smithy.model.traits.JsonNameTrait$Provider software.amazon.smithy.model.traits.LengthTrait$Provider software.amazon.smithy.model.traits.MediaTypeTrait$Provider -software.amazon.smithy.model.traits.OutputEventStreamTrait$Provider software.amazon.smithy.model.traits.PaginatedTrait$Provider software.amazon.smithy.model.traits.PatternTrait$Provider software.amazon.smithy.model.traits.PrivateTrait$Provider 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 d5d93819b39..1252fd14e99 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 @@ -465,15 +465,15 @@ structure eventPayload {} @tags(["diff.error.const"]) structure eventHeader {} -/// Defines the operation input member name that contains an event stream. -@trait(selector: "operation:test(-[input]->)") -@tags(["diff.error.const"]) -string inputEventStream - -/// Defines the operation output member name that contains an event stream. -@trait(selector: "operation:test(-[output]->)") +/// Binds an input or output member as an event stream. +/// The targeted member must be targeted by the input or output of +/// an operation, and must target a union or structure. The +/// targeted member must not be marked as required. +@trait(selector: "operation -[input, output]-> structure > :test(member > :each(structure, union))", + structurallyExclusive: true, + conflicts: [required]) @tags(["diff.error.const"]) -string outputEventStream +structure eventStream {} /// Indicates that a string value MUST contain a valid shape ID. /// diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/EventStreamIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/EventStreamIndexTest.java index 804240cfa42..ad66b311610 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/EventStreamIndexTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/EventStreamIndexTest.java @@ -22,15 +22,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Optional; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ShapeId; public class EventStreamIndexTest { - private static Model model = Model.assembler() - .addImport(HttpBindingIndex.class.getResource("event-stream-index.json")) - .assemble() - .unwrap(); + private static Model model; + + @BeforeAll + public static void beforeAll() { + model = Model.assembler() + .addImport(EventStreamIndexTest.class.getResource("event-stream-index.json")) + .assemble() + .unwrap(); + } @Test public void providesEmptyOptionalWhenNotShape() { @@ -113,4 +119,12 @@ public void returnsEventStreamOutputInformation() { assertThat(input, not(equalTo(output))); } + + @Test + public void loadsSingleEventEventStreams() { + EventStreamIndex index = model.getKnowledge(EventStreamIndex.class); + EventStreamInfo info = index.getInputInfo(ShapeId.from("example.smithy#SingleEventOperation")).get(); + + assertThat(info.getEvents(), hasKey("messages")); + } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.errors index 86fd9563949..423fbaf2ecb 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.errors +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.errors @@ -1,8 +1,5 @@ -[ERROR] ns.foo#EventStreamInputMemberMissing: Operation input member `events` was not found for the `inputEventStream` trait. | EventStream -[ERROR] ns.foo#EventStreamInputMemberMustNotBeRequired: Operation input member `ns.foo#EventStreamInputMemberMustNotBeRequiredInput$events` is referenced by an `inputEventStream` trait, so it cannot be marked as required. | EventStream -[ERROR] ns.foo#EventStreamInputMissing: Trait `inputEventStream` cannot be applied to `ns.foo#EventStreamInputMissing`. This trait may only be applied to shapes that match the following selector: operation:test(-[input]->) | TraitTarget -[ERROR] ns.foo#EventStreamInputMissing: operation shape has a `input` relationship to an unresolved shape `ns.foo#EventStreamInputMissingInput` | Target -[ERROR] ns.foo#EventStreamNonInput: Operation has the `inputEventStream` but does not define an input structure. | EventStream -[ERROR] ns.foo#EventStreamNonInput: Trait `inputEventStream` cannot be applied to `ns.foo#EventStreamNonInput`. This trait may only be applied to shapes that match the following selector: operation:test(-[input]->) | TraitTarget +[ERROR] ns.foo#EventStreamInputMemberMustNotBeRequiredInput$events: Found conflicting traits on member shape: `eventStream` conflicts with `required` | TraitConflict +[ERROR] ns.foo#EventStreamReferencesInvalidShape: Operation input member `ns.foo#EventStreamReferencesInvalidShapeInput$events` is referenced by the `eventStream` trait, so it must target a structure or union, but found smithy.api#String, a string. | EventStream +[ERROR] ns.foo#EventStreamReferencesInvalidShapeInput$events: Trait `eventStream` cannot be applied to `ns.foo#EventStreamReferencesInvalidShapeInput$events`. This trait may only be applied to shapes that match the following selector: operation -[input, output]-> structure > :test(member > :each(structure, union)) | TraitTarget [ERROR] ns.foo#EventStreamReferencesInvalidMultiEventShape: Operation input member `ns.foo#EventStreamReferencesInvalidMultiEventShapeInput$events` targets an invalid union `ns.foo#InvalidUnion`; each member of an event stream union must target a structure shape, but the following union members do not: [b, c] | EventStream -[ERROR] ns.foo#EventStreamReferencesInvalidShape: Operation input member `ns.foo#EventStreamReferencesInvalidShapeInput$events` is referenced by the `inputEventStream` trait, so it must target a structure or union, but found smithy.api#String, a string. | EventStream +[ERROR] ns.foo#InvalidEventStreamTargeting$member: This shape has an invalid `MEMBER_TARGET` relationship to a structure, `ns.foo#EventStreamReferencesInvalidMultiEventShapeInput`, that contains an event stream | EventStream diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.json index a7d96241468..239495e2308 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/event-stream-validator.json @@ -5,35 +5,31 @@ "ValidSingleEventInputOperation": { "type": "operation", - "input": "ValidSingleEventInput", - "inputEventStream": "events" + "input": "ValidSingleEventInput" }, "ValidSingleEventOutputOperation": { "type": "operation", - "output": "ValidSingleEventOutput", - "outputEventStream": "events" + "output": "ValidSingleEventOutput" }, "ValidSingleEventBidirectionalOperation": { "type": "operation", "input": "ValidSingleEventInput", - "inputEventStream": "events", - "output": "ValidSingleEventOutput", - "outputEventStream": "events" + "output": "ValidSingleEventOutput" }, "ValidSingleEventInput": { "type": "structure", "members": { - "events": {"target": "Event"} + "events": {"target": "Event", "eventStream": true} } }, "ValidSingleEventOutput": { "type": "structure", "members": { - "events": {"target": "Event"} + "events": {"target": "Event", "eventStream": true} } }, @@ -43,35 +39,31 @@ "ValidMultiEventInputOperation": { "type": "operation", - "input": "ValidMultiEventInput", - "inputEventStream": "events" + "input": "ValidMultiEventInput" }, "ValidMultiEventOutputOperation": { "type": "operation", - "output": "ValidMultiEventOutput", - "outputEventStream": "events" + "output": "ValidMultiEventOutput" }, "ValidMultiEventBidirectionalOperation": { "type": "operation", "input": "ValidMultiEventInput", - "inputEventStream": "events", - "output": "ValidMultiEventOutput", - "outputEventStream": "events" + "output": "ValidMultiEventOutput" }, "ValidMultiEventInput": { "type": "structure", "members": { - "events": {"target": "Event"} + "events": {"target": "ValidUnion", "eventStream": true} } }, "ValidMultiEventOutput": { "type": "structure", "members": { - "events": {"target": "Event"} + "events": {"target": "ValidUnion", "eventStream": true} } }, @@ -83,67 +75,41 @@ } }, - "EventStreamNonInput": { - "type": "operation", - "inputEventStream": "events" - }, - - "EventStreamInputMissing": { - "type": "operation", - "input": "EventStreamInputMissingInput", - "inputEventStream": "events" - }, - - "EventStreamInputMemberMissing": { - "type": "operation", - "input": "EventStreamInputMemberMissingInput", - "inputEventStream": "events" - }, - - "EventStreamInputMemberMissingInput": { - "type": "structure", - "members": { - "foo": {"target": "smithy.api#String"} - } - }, - "EventStreamInputMemberMustNotBeRequired": { "type": "operation", - "input": "EventStreamInputMemberMustNotBeRequiredInput", - "inputEventStream": "events" + "input": "EventStreamInputMemberMustNotBeRequiredInput" }, "EventStreamInputMemberMustNotBeRequiredInput": { "type": "structure", "members": { - "events": {"target": "Event", "required": true} + "events": {"target": "Event", "required": true, "eventStream": true} } }, "EventStreamReferencesInvalidShape": { "type": "operation", - "input": "EventStreamReferencesInvalidShapeInput", - "inputEventStream": "events" + "input": "EventStreamReferencesInvalidShapeInput" }, "EventStreamReferencesInvalidShapeInput": { "type": "structure", "members": { - "events": {"target": "smithy.api#String"} + "events": {"target": "smithy.api#String", "eventStream": true} } }, "EventStreamReferencesInvalidMultiEventShape": { "type": "operation", - "input": "EventStreamReferencesInvalidMultiEventShapeInput", - "inputEventStream": "events" + "input": "EventStreamReferencesInvalidMultiEventShapeInput" }, "EventStreamReferencesInvalidMultiEventShapeInput": { "type": "structure", "members": { "events": { - "target": "InvalidUnion" + "target": "InvalidUnion", + "eventStream": true } } }, @@ -155,6 +121,13 @@ "b": {"target": "smithy.api#String"}, "c": {"target": "smithy.api#String"} } + }, + + "InvalidEventStreamTargeting": { + "type": "list", + "member": { + "target": "EventStreamReferencesInvalidMultiEventShapeInput" + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/event-stream-index.json b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/event-stream-index.json index f183a7335e0..3445bf2a409 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/event-stream-index.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/event-stream-index.json @@ -21,9 +21,7 @@ "EventStreamOperation": { "type": "operation", "input": "EventStreamOperationInput", - "inputEventStream": "c", - "output": "EventStreamOperationOutput", - "outputEventStream": "c" + "output": "EventStreamOperationOutput" }, "EventStreamOperationInput": { @@ -31,7 +29,7 @@ "members": { "a": {"target": "smithy.api#String"}, "b": {"target": "smithy.api#Integer"}, - "c": {"target": "InputEventStream"} + "c": {"target": "InputEventStream", "eventStream": true} } }, @@ -40,7 +38,7 @@ "members": { "a": {"target": "smithy.api#String"}, "b": {"target": "smithy.api#Integer"}, - "c": {"target": "OutputEventStream"} + "c": {"target": "OutputEventStream", "eventStream": true} } }, @@ -65,6 +63,18 @@ "members": { "foo": {"target": "smithy.api#String"} } + }, + + "SingleEventOperation": { + "type": "operation", + "input": "SingleEventOperationInput" + }, + + "SingleEventOperationInput": { + "type": "structure", + "members": { + "messages": {"target": "EventStructure", "eventStream": true} + } } } } diff --git a/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java new file mode 100644 index 00000000000..1d7d2fab6bf --- /dev/null +++ b/smithy-mqtt-traits/src/main/java/software/amazon/smithy/mqtt/traits/validators/MqttPublishInputValidator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.mqtt.traits.validators; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeIndex; +import software.amazon.smithy.model.traits.EventStreamTrait; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.mqtt.traits.PublishTrait; +import software.amazon.smithy.utils.OptionalUtils; + +/** + * Publish operations must not contain event streams. + */ +public class MqttPublishInputValidator extends AbstractValidator { + @Override + public List validate(Model model) { + ShapeIndex index = model.getShapeIndex(); + return index.shapes(OperationShape.class) + .filter(shape -> shape.hasTrait(PublishTrait.class)) + .flatMap(shape -> validateOperation(index, shape)) + .collect(Collectors.toList()); + } + + private Stream validateOperation(ShapeIndex index, OperationShape operation) { + return OptionalUtils.stream(operation.getInput().flatMap(index::getShape).flatMap(Shape::asStructureShape)) + .flatMap(input -> input.getAllMembers().values().stream() + .filter(member -> member.hasTrait(EventStreamTrait.class)) + .map(member -> error(member, String.format( + "The input of `smithy.mqtt#publish` operations cannot contain event streams, " + + "and this member is used as part of the input of the `%s` operation.", + operation.getId())))); + } +} diff --git a/smithy-mqtt-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-mqtt-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator index 6c55531f2fe..4b2b38e1897 100644 --- a/smithy-mqtt-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator +++ b/smithy-mqtt-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -1,3 +1,4 @@ +software.amazon.smithy.mqtt.traits.validators.MqttPublishInputValidator software.amazon.smithy.mqtt.traits.validators.MqttSubscribeInputValidator software.amazon.smithy.mqtt.traits.validators.MqttSubscribeOutputValidator software.amazon.smithy.mqtt.traits.validators.MqttTopicConflictValidator diff --git a/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy b/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy index fdc855f3999..7e2f2990563 100644 --- a/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy +++ b/smithy-mqtt-traits/src/main/resources/META-INF/smithy/smithy.api.mqtt.smithy @@ -3,13 +3,13 @@ $version: "0.4.0" namespace smithy.mqtt @trait(selector: "operation:not(-[output]->)", - conflicts: ["smithy.mqtt#subscribe", "inputEventStream"]) + conflicts: ["smithy.mqtt#subscribe"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". @pattern("^[^#+]+$") string publish -@trait(selector: "operation[trait|outputEventStream]", +@trait(selector: "operation:test(-[output]-> structure > member[trait|eventStream])", conflicts: ["smithy.mqtt#publish"]) @tags(["diff.error.const"]) // Matches one or more characters that are not "#" or "+". diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/dropped-mqtt-headers.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/dropped-mqtt-headers.smithy index 849c7040697..dcd0f2ca996 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/dropped-mqtt-headers.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/dropped-mqtt-headers.smithy @@ -4,12 +4,12 @@ namespace smithy.example @smithy.mqtt#subscribe("events") -@outputEventStream("messages") operation Foo(FooInput) -> FooOutput structure FooInput {} structure FooOutput { + @eventStream messages: Event } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/job-service.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/job-service.smithy index 6547c718c0f..a3c6cb4193d 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/job-service.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/job-service.smithy @@ -30,6 +30,7 @@ service IotJobs { // ------ Service-wide error ------- structure RejectedResponse { + @eventStream messages: RejectedError } @@ -64,13 +65,11 @@ string RejectedErrorCode operation PublishGetPendingJobExecutions(GetPendingJobExecutionsRequest) @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/get/accepted") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions") operation SubscribeToGetPendingJobExecutionsAccepted(GetPendingJobExecutionsSubscriptionRequest) -> GetPendingJobExecutionsSubscriptionResponse @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/get/rejected") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions") operation SubscribeToGetPendingJobExecutionsRejected(GetPendingJobExecutionsSubscriptionRequest) -> RejectedResponse @@ -90,6 +89,7 @@ structure GetPendingJobExecutionsSubscriptionRequest { } structure GetPendingJobExecutionsSubscriptionResponse { + @eventStream messages: GetPendingJobExecutionsResponse, } @@ -122,13 +122,11 @@ structure JobExecutionSummary { operation PublishStartNextPendingJobExecution(StartNextPendingJobExecutionRequest) @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/start-next/accepted") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution") operation SubscribeToStartNextPendingJobExecutionAccepted(StartNextPendingJobExecutionSubscriptionRequest) -> StartNextPendingJobExecutionSubscriptionResponse @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/start-next/rejected") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution") operation SubscribeToStartNextPendingJobExecutionRejected(StartNextPendingJobExecutionSubscriptionRequest) -> RejectedResponse @@ -156,6 +154,7 @@ structure StartNextPendingJobExecutionSubscriptionRequest { } structure StartNextPendingJobExecutionSubscriptionResponse { + @eventStream messages: StartNextJobExecutionResponse } @@ -199,13 +198,11 @@ string JobStatus operation PublishDescribeJobExecution(DescribeJobExecutionRequest) @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/{jobId}/get/accepted") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution") operation SubscribeToDescribeJobExecutionAccepted(DescribeJobExecutionSubscriptionRequest) -> DescribeJobExecutionSubscriptionResponse @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/{jobId}/get/rejected") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution") operation SubscribeToDescribeJobExecutionRejected(DescribeJobExecutionSubscriptionRequest) -> RejectedResponse @@ -236,6 +233,7 @@ structure DescribeJobExecutionSubscriptionRequest { } structure DescribeJobExecutionSubscriptionResponse { + @eventStream messages: DescribeJobExecutionResponse } @@ -257,13 +255,11 @@ structure DescribeJobExecutionResponse { operation PublishUpdateJobExecution(UpdateJobExecutionRequest) @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/{jobId}/update/accepted") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution") operation SubscribeToUpdateJobExecutionAccepted(UpdateJobExecutionSubscriptionRequest) -> UpdateJobExecutionSubscriptionResponse @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/{jobId}/update/rejected") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution") operation SubscribeToUpdateJobExecutionRejected(UpdateJobExecutionSubscriptionRequest) -> RejectedResponse @@ -300,6 +296,7 @@ structure UpdateJobExecutionSubscriptionRequest { } structure UpdateJobExecutionSubscriptionResponse { + @eventStream messages: UpdateJobExecutionResponse } @@ -328,7 +325,6 @@ document JobDocument // ------- JobExecutionsChanged ---------- @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/notify") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-jobexecutionschanged") operation SubscribeToJobExecutionsChangedEvents(JobExecutionsChangedSubscriptionRequest) -> JobExecutionsChangedSubscriptionResponse @@ -340,6 +336,7 @@ structure JobExecutionsChangedSubscriptionRequest { } structure JobExecutionsChangedSubscriptionResponse { + @eventStream messages: JobExecutionsChangedEvent, } @@ -360,7 +357,6 @@ map JobExecutionsChangedJobs { // ------- NextJobExecutionChanged ---------- @smithy.mqtt#subscribe("$aws/things/{thingName}/jobs/notify-next") -@outputEventStream("messages") @externalDocumentation("https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-nextjobexecutionchanged") operation SubscribeToNextJobExecutionChangedEvents(NextJobExecutionChangedSubscriptionRequest) -> NextJobExecutionChangedSubscriptionResponse @@ -372,6 +368,7 @@ structure NextJobExecutionChangedSubscriptionRequest { } structure NextJobExecutionChangedSubscriptionResponse { + @eventStream messages: NextJobExecutionChangedEvent, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/mqtt-operations-with-errors.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/mqtt-operations-with-errors.smithy index 695c0c17c83..0b543136926 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/mqtt-operations-with-errors.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/mqtt-operations-with-errors.smithy @@ -3,10 +3,10 @@ namespace smithy.example @smithy.mqtt#subscribe("event1") -@outputEventStream("messages") operation Foo() -> FooOutput errors [Error] structure FooOutput { + @eventStream messages: Event1, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.errors b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.errors index 015c9e868dd..2c688e2cb9f 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.errors +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.errors @@ -1 +1 @@ -[ERROR] smithy.example#Publish: Found conflicting traits on operation shape: `smithy.mqtt#publish` conflicts with `inputEventStream` | TraitConflict +[ERROR] smithy.example#PublishInput$messages: The input of `smithy.mqtt#publish` operations cannot contain event streams, and this member is used as part of the input of the `smithy.example#Publish` operation. | MqttPublishInput diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.smithy index 831f831d2c7..29d71a54cf0 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/publish-with-eventstream.smithy @@ -1,10 +1,10 @@ namespace smithy.example @smithy.mqtt#publish("foo") -@inputEventStream("messages") // Invalid operation Publish(PublishInput) structure PublishInput { + @eventStream // invalid messages: Event, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-input-missing-label.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-input-missing-label.smithy index f0092580deb..6f018cdb0d8 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-input-missing-label.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-input-missing-label.smithy @@ -8,7 +8,6 @@ use smithy.mqtt#topicLabel use smithy.mqtt#subscribe @subscribe("events/{foo}") -@outputEventStream("messages") operation Foo(FooInput) -> FooOutput structure FooInput { @@ -20,6 +19,7 @@ structure FooInput { } structure FooOutput { + @eventStream messages: Event1, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-missing-output.errors b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-missing-output.errors index 98eb075d6d6..5169375eb92 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-missing-output.errors +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-missing-output.errors @@ -1 +1 @@ -[ERROR] smithy.example#Foo: Trait `smithy.mqtt#subscribe` cannot be applied to `smithy.example#Foo`. This trait may only be applied to shapes that match the following selector: operation[trait|outputEventStream] | TraitTarget +[ERROR] smithy.example#Foo: Trait `smithy.mqtt#subscribe` cannot be applied to `smithy.example#Foo`. This trait may only be applied to shapes that match the following selector: operation:test(-[output]-> structure > member[trait|eventStream]) | TraitTarget diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-initial-event.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-initial-event.smithy index 63048563ad6..e8fb77943b7 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-initial-event.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-initial-event.smithy @@ -3,11 +3,11 @@ namespace smithy.example @smithy.mqtt#subscribe("events") -@outputEventStream("messages") operation Foo() -> FooOutput structure FooOutput { badMember: smithy.api#String, // <-- Erroneous initial event member + @eventStream messages: Event1, } diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-missing-event-stream.errors b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-missing-event-stream.errors index 98eb075d6d6..5169375eb92 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-missing-event-stream.errors +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/subscribe-operation-missing-event-stream.errors @@ -1 +1 @@ -[ERROR] smithy.example#Foo: Trait `smithy.mqtt#subscribe` cannot be applied to `smithy.example#Foo`. This trait may only be applied to shapes that match the following selector: operation[trait|outputEventStream] | TraitTarget +[ERROR] smithy.example#Foo: Trait `smithy.mqtt#subscribe` cannot be applied to `smithy.example#Foo`. This trait may only be applied to shapes that match the following selector: operation:test(-[output]-> structure > member[trait|eventStream]) | TraitTarget diff --git a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/topic-conflicts.smithy b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/topic-conflicts.smithy index 0322a8dfac1..28f4992e892 100644 --- a/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/topic-conflicts.smithy +++ b/smithy-mqtt-traits/src/test/resources/software/amazon/smithy/mqtt/traits/testsuite/topic-conflicts.smithy @@ -12,10 +12,10 @@ structure BInput {} // Conflicts with A, B @smithy.mqtt#subscribe("a") -@outputEventStream("messages") operation C() -> COutput structure COutput { + @eventStream messages: EmptyEvent, } @@ -31,10 +31,10 @@ structure DInput {} operation E(DInput) @smithy.mqtt#subscribe("b") -@outputEventStream("messages") operation F() -> FOutput structure FOutput { + @eventStream messages: DInputEvent, } diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraits.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraits.java index 3968f775ba3..742af95baf4 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraits.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraits.java @@ -35,7 +35,7 @@ public final class UnsupportedTraits implements OpenApiMapper { private static final Logger LOGGER = Logger.getLogger(UnsupportedTraits.class.getName()); private static final Set TRAITS = SetUtils.of( - "inputEventStream", "outputEventStream", "eventPayload", "eventHeader", "streaming"); + "eventStream", "eventPayload", "eventHeader", "streaming"); @Override public byte getOrder() {