From 1dcd97bdbd08415379e21cd00d445e7ee2dc616e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Fri, 6 Mar 2026 15:07:47 -0800 Subject: [PATCH 1/4] Fix encoding/decoding of errors and exceptions events --- .../java/aws/events/AwsEventShapeDecoder.java | 31 ++- .../aws/events/AwsEventShapeDecoderTest.java | 88 +++++-- .../aws/events/AwsEventShapeEncoderTest.java | 62 ++++- .../java/aws/events/model/BlobEvent.java | 1 + .../aws/events/model/BodyAndHeaderEvent.java | 1 + .../events/model/EventStreamWithError.java | 206 ++++++++++++++++ .../EventStreamingTestServiceApiService.java | 2 +- .../events/model/GeneratedSchemaIndex.java | 4 + .../aws/events/model/HeadersOnlyEvent.java | 1 + .../smithy/java/aws/events/model/MyError.java | 155 ++++++++++++ .../smithy/java/aws/events/model/Schemas.java | 54 ++++- .../java/aws/events/model/StringEvent.java | 1 + .../java/aws/events/model/StructureEvent.java | 1 + .../aws/events/model/TestEventStream.java | 229 +++++++----------- .../java/aws/events/model/TestOperation.java | 15 +- .../aws/events/model/TestOperationInput.java | 6 +- .../aws/events/model/TestOperationOutput.java | 1 + .../model/TestOperationWithException.java | 117 +++++++++ .../TestOperationWithExceptionInput.java | 162 +++++++++++++ .../TestOperationWithExceptionOutput.java | 162 +++++++++++++ 20 files changed, 1113 insertions(+), 186 deletions(-) create mode 100644 aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamWithError.java create mode 100644 aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/MyError.java create mode 100644 aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithException.java create mode 100644 aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionInput.java create mode 100644 aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionOutput.java diff --git a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java index 26a180b9a..f285506f2 100644 --- a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java +++ b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java @@ -21,6 +21,7 @@ import software.amazon.smithy.java.core.serde.SpecificShapeDeserializer; import software.amazon.smithy.java.core.serde.event.EventDecoder; import software.amazon.smithy.java.core.serde.event.EventStream; +import software.amazon.smithy.java.core.serde.event.EventStreamingException; /** * A decoder for AWS events @@ -58,8 +59,28 @@ public SerializableStruct decode(AwsEventFrame frame) { private E decodeEvent(AwsEventFrame frame) { var message = frame.unwrap(); - // TODO Add support for :message-type other than "event". + var messageType = getMessageType(message); + if ("error".equals(messageType)) { + decodeErrorAndThrow(message); + } else if ("exception".equals(messageType)) { + return decodeModeledException(message); + } else if (!"event".equals(messageType)) { + throw new IllegalArgumentException("Invalid message type: " + messageType); + } var eventType = getEventType(message); + return decodePayload(eventType, message); + } + + private E decodeModeledException(Message message) { + var exceptionTypeHeader = message.getHeaders().get(":exception-type"); + if (exceptionTypeHeader == null) { + throw new IllegalStateException("expected headers to have ':exception-type' header"); + } + var exceptionType = exceptionTypeHeader.getString(); + return decodePayload(exceptionType, message); + } + + private E decodePayload(String eventType, Message message) { var memberSchema = eventSchema.member(eventType); if (memberSchema == null) { throw new IllegalArgumentException("Unsupported event type: " + eventType); @@ -75,6 +96,14 @@ private E decodeEvent(AwsEventFrame frame) { return builder.build(); } + private void decodeErrorAndThrow(Message message) { + var errorCodeHeader = message.getHeaders().get(":error-code"); + var errorCode = errorCodeHeader != null ? errorCodeHeader.getString() : "unknown error code"; + var errorMessageHeder = message.getHeaders().get(":error-message"); + var errorMessage = errorMessageHeder != null ? errorMessageHeder.getString() : "unknown error message"; + throw new EventStreamingException(errorCode, errorMessage); + } + @Override public IR decodeInitialEvent(AwsEventFrame frame, EventStream eventStream) { var message = frame.unwrap(); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoderTest.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoderTest.java index 66ce7d80c..7b8932401 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoderTest.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoderTest.java @@ -7,19 +7,26 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.junit.jupiter.api.Test; import software.amazon.eventstream.Message; import software.amazon.smithy.java.aws.events.model.BodyAndHeaderEvent; +import software.amazon.smithy.java.aws.events.model.EventStreamWithError; import software.amazon.smithy.java.aws.events.model.HeadersOnlyEvent; +import software.amazon.smithy.java.aws.events.model.MyError; import software.amazon.smithy.java.aws.events.model.StringEvent; import software.amazon.smithy.java.aws.events.model.StructureEvent; import software.amazon.smithy.java.aws.events.model.TestEventStream; import software.amazon.smithy.java.aws.events.model.TestOperation; import software.amazon.smithy.java.aws.events.model.TestOperationOutput; +import software.amazon.smithy.java.aws.events.model.TestOperationWithException; +import software.amazon.smithy.java.core.schema.ApiOperation; +import software.amazon.smithy.java.core.schema.SerializableStruct; import software.amazon.smithy.java.core.serde.Codec; +import software.amazon.smithy.java.core.serde.event.EventStreamingException; import software.amazon.smithy.java.json.JsonCodec; class AwsEventShapeDecoderTest { @@ -36,7 +43,7 @@ public void testDecodeInitialResponse() { var frame = new AwsEventFrame(message); // Act - var struct = createDecoder().decodeInitialEvent(frame, null); + var struct = createDecoder(TestOperation.instance()).decodeInitialEvent(frame, null); // Assert assertInstanceOf(TestOperationOutput.class, struct); @@ -59,12 +66,12 @@ public void testDecodeHeadersOnlyMember() { var frame = new AwsEventFrame(message); // Act - var struct = createDecoder().decode(frame); + var struct = createDecoder(TestOperation.instance()).decode(frame); // Assert assertInstanceOf(TestEventStream.class, struct); var actual = (TestEventStream) struct; - assertEquals(TestEventStream.Type.headersOnlyMember, actual.type()); + assertInstanceOf(TestEventStream.HeadersOnlyMemberMember.class, actual); var expected = TestEventStream.builder() .headersOnlyMember(HeadersOnlyEvent.builder().sequenceNum(123).build()) .build(); @@ -82,12 +89,12 @@ public void testDecodeStructureMember() { var frame = new AwsEventFrame(message); // Act - var struct = createDecoder().decode(frame); + var struct = createDecoder(TestOperation.instance()).decode(frame); // Assert assertInstanceOf(TestEventStream.class, struct); var actual = (TestEventStream) struct; - assertEquals(TestEventStream.Type.structureMember, actual.type()); + assertInstanceOf(TestEventStream.StructureMemberMember.class, actual); var expected = TestEventStream.builder() .structureMember(StructureEvent.builder().foo("memberFooValue").build()) .build(); @@ -106,12 +113,12 @@ public void testDecodeBodyAndHeaderMember() { var frame = new AwsEventFrame(message); // Act - var struct = createDecoder().decode(frame); + var struct = createDecoder(TestOperation.instance()).decode(frame); // Assert assertInstanceOf(TestEventStream.class, struct); var actual = (TestEventStream) struct; - assertEquals(TestEventStream.Type.bodyAndHeaderMember, actual.type()); + assertInstanceOf(TestEventStream.BodyAndHeaderMemberMember.class, actual); var expected = TestEventStream.builder() .bodyAndHeaderMember(BodyAndHeaderEvent.builder() .intMember(123) @@ -127,29 +134,82 @@ public void testDecodeStringMember() { var headers = new AwsEventShapeEncoderTest.HeadersBuilder() .contentType("text/json") .eventType("stringMember") - .build();; + .build(); var message = new Message(headers, "\"hello world!\"".getBytes(StandardCharsets.UTF_8)); var frame = new AwsEventFrame(message); // Act - var struct = createDecoder().decode(frame); + var struct = createDecoder(TestOperation.instance()).decode(frame); // Assert assertInstanceOf(TestEventStream.class, struct); var actual = (TestEventStream) struct; - assertEquals(TestEventStream.Type.stringMember, actual.type()); + assertInstanceOf(TestEventStream.StringMemberMember.class, actual); var expected = TestEventStream.builder() .stringMember(StringEvent.builder().payload("hello world!").build()) .build(); assertEquals(expected, actual); } + @Test + public void testDecodeExceptionMember() { + // Arrange + var headers = new AwsEventShapeEncoderTest.HeadersBuilder() + .contentType("text/json") + .messageType("exception") + .exceptionType("modeledErrorMember") + .build(); + var message = new Message(headers, "{\"message\":\"Client exception\"}".getBytes(StandardCharsets.UTF_8)); + var frame = new AwsEventFrame(message); + + // Act + var struct = createDecoder(TestOperationWithException.instance()).decode(frame); + + // Assert + assertInstanceOf(EventStreamWithError.class, struct); + var actual = (EventStreamWithError) struct; + assertInstanceOf(EventStreamWithError.ModeledErrorMemberMember.class, actual); + var expected = EventStreamWithError.builder() + .modeledErrorMember(MyError.builder().message("Client exception").build()) + .build(); + assertEquals(((EventStreamWithError.ModeledErrorMemberMember) expected).getValue().getMessage(), + ((EventStreamWithError.ModeledErrorMemberMember) actual).getValue().getMessage()); + } + + @Test + public void testDecodeError() { + // Arrange + var headers = new AwsEventShapeEncoderTest.HeadersBuilder() + .messageType("error") + .put(":error-code", "InternalFailure") + .put(":error-message", "An internal server error occurred") + .build(); + var message = new Message(headers, new byte[0]); + var frame = new AwsEventFrame(message); + + // Act + Exception except = null; + try { + createDecoder(TestOperationWithException.instance()).decode(frame); + } catch (Exception e) { + except = e; + } + + // Assert + assertNotNull(except); + assertInstanceOf(EventStreamingException.class, except); + var eventStreamingException = (EventStreamingException) except; + assertEquals(eventStreamingException.getErrorCode(), "InternalFailure"); + assertEquals(eventStreamingException.getMessage(), "An internal server error occurred"); + } + @SuppressWarnings("unchecked") - static AwsEventShapeDecoder createDecoder() { + static AwsEventShapeDecoder createDecoder(ApiOperation operation) { return new AwsEventShapeDecoder<>(InitialEventType.INITIAL_RESPONSE, - () -> TestOperation.instance().outputBuilder(), // output builder - (Supplier) TestOperation.instance().outputEventBuilderSupplier(), - TestOperation.instance().outputStreamMember(), + () -> operation.outputBuilder(), // output builder + (Supplier) operation.outputEventBuilderSupplier(), + operation.outputStreamMember(), createJsonCodec()); } diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeEncoderTest.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeEncoderTest.java index fb6ba54ea..5f6e65f75 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeEncoderTest.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/AwsEventShapeEncoderTest.java @@ -13,11 +13,15 @@ import software.amazon.eventstream.HeaderValue; import software.amazon.smithy.java.aws.events.model.BodyAndHeaderEvent; import software.amazon.smithy.java.aws.events.model.HeadersOnlyEvent; +import software.amazon.smithy.java.aws.events.model.MyError; import software.amazon.smithy.java.aws.events.model.StringEvent; import software.amazon.smithy.java.aws.events.model.StructureEvent; import software.amazon.smithy.java.aws.events.model.TestEventStream; import software.amazon.smithy.java.aws.events.model.TestOperation; import software.amazon.smithy.java.aws.events.model.TestOperationInput; +import software.amazon.smithy.java.aws.events.model.TestOperationWithException; +import software.amazon.smithy.java.core.schema.ApiOperation; +import software.amazon.smithy.java.core.schema.SerializableStruct; import software.amazon.smithy.java.core.serde.Codec; import software.amazon.smithy.java.core.serde.event.EventStreamingException; import software.amazon.smithy.java.json.JsonCodec; @@ -27,7 +31,7 @@ class AwsEventShapeEncoderTest { @Test public void testEncodeInitialRequest() { // Arrange - var encoder = createEncoder(); + var encoder = createEncoder(TestOperation.instance()); var event = TestOperationInput.builder() .headerString("headerValue") .inputStringMember("inputStringValue") @@ -48,7 +52,7 @@ public void testEncodeInitialRequest() { @Test public void testEncodeHeadersOnlyMember() { // Arrange - var encoder = createEncoder(); + var encoder = createEncoder(TestOperation.instance()); var event = TestEventStream.builder() .headersOnlyMember(HeadersOnlyEvent.builder().sequenceNum(123).build()) .build(); @@ -69,7 +73,7 @@ public void testEncodeHeadersOnlyMember() { @Test public void testEncodeStructureMember() { // Arrange - var encoder = createEncoder(); + var encoder = createEncoder(TestOperation.instance()); var event = TestEventStream.builder() .structureMember(StructureEvent.builder().foo("memberFooValue").build()) .build(); @@ -89,7 +93,7 @@ public void testEncodeStructureMember() { @Test public void testEncodeBodyAndHeaderMember() { // Arrange - var encoder = createEncoder(); + var encoder = createEncoder(TestOperation.instance()); var event = TestEventStream.builder() .bodyAndHeaderMember(BodyAndHeaderEvent.builder() .intMember(123) @@ -113,7 +117,7 @@ public void testEncodeBodyAndHeaderMember() { @Test public void testEncodeStringMember() { // Arrange - var encoder = createEncoder(); + var encoder = createEncoder(TestOperation.instance()); var event = TestEventStream.builder() .stringMember(StringEvent.builder().payload("hello world!").build()) .build(); @@ -130,9 +134,48 @@ public void testEncodeStringMember() { assertEquals("\"hello world!\"", new String(result.unwrap().getPayload())); } - static AwsEventShapeEncoder createEncoder() { + @Test + public void testEncodeException() { + // Arrange + var encoder = createEncoder(TestOperationWithException.instance()); + var exception = MyError.builder().message("Event stream exception").build(); + + // Act + var result = encoder.encodeFailure(exception); + + // Assert + var expectedHeaders = new HeadersBuilder() + .contentType("text/json") + .messageType("exception") + .exceptionType("modeledErrorMember") + .build(); + assertEquals(expectedHeaders, result.unwrap().getHeaders()); + assertEquals("{\"message\":\"Event stream exception\"}", new String(result.unwrap().getPayload())); + } + + @Test + public void testEncodeError() { + // Arrange + var encoder = createEncoder(TestOperationWithException.instance()); + var exception = new NullPointerException("something caused a null pointer exception"); + + // Act + var result = encoder.encodeFailure(exception); + + // Assert + var expectedHeaders = new HeadersBuilder() + .messageType("error") + .put(":error-message", "Internal Server Error") + .put(":error-code", "InternalServerException") + .build(); + assertEquals(expectedHeaders, result.unwrap().getHeaders()); + assertEquals("", new String(result.unwrap().getPayload())); + } + + static AwsEventShapeEncoder createEncoder(ApiOperation operation) { return new AwsEventShapeEncoder(InitialEventType.INITIAL_REQUEST, - TestOperation.instance().inputStreamMember(), // event schema + operation.outputStreamMember(), // event schema createJsonCodec(), // codec "text/json", (e) -> new EventStreamingException("InternalServerException", "Internal Server Error")); @@ -164,6 +207,11 @@ public HeadersBuilder eventType(String eventType) { return this; } + public HeadersBuilder exceptionType(String eventType) { + headers.put(":exception-type", HeaderValue.fromString(eventType)); + return this; + } + public HeadersBuilder put(String name, String value) { headers.put(name, HeaderValue.fromString(value)); return this; diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BlobEvent.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BlobEvent.java index f7e41b837..c4534d7d0 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BlobEvent.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BlobEvent.java @@ -149,6 +149,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.payload(de.readBlob(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BodyAndHeaderEvent.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BodyAndHeaderEvent.java index 66e817c6f..dfe7b9b6d 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BodyAndHeaderEvent.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/BodyAndHeaderEvent.java @@ -171,6 +171,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.intMember(de.readInteger(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamWithError.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamWithError.java new file mode 100644 index 000000000..36831c18e --- /dev/null +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamWithError.java @@ -0,0 +1,206 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.events.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public sealed interface EventStreamWithError extends SerializableStruct { + Schema $SCHEMA = Schemas.EVENT_STREAM_WITH_ERROR; + + ShapeId $ID = $SCHEMA.id(); + + T getValue(); + + @Override + default Schema schema() { + return $SCHEMA; + } + + @Override + default T getMemberValue(Schema member) { + return SchemaUtils.validateMemberInSchema($SCHEMA, member, getValue()); + } + + @SmithyGenerated + record ModeledErrorMemberMember(MyError modeledErrorMember) implements EventStreamWithError { + private static final Schema $SCHEMA_MODELED_ERROR_MEMBER = $SCHEMA.member("modeledErrorMember"); + public ModeledErrorMemberMember { + Objects.requireNonNull(modeledErrorMember, "Union value cannot be null"); + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeStruct($SCHEMA_MODELED_ERROR_MEMBER, modeledErrorMember); + } + + @Override + @SuppressWarnings("unchecked") + public MyError getValue() { + return modeledErrorMember; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + } + + @SmithyGenerated + record StringMemberMember(StringEvent stringMember) implements EventStreamWithError { + private static final Schema $SCHEMA_STRING_MEMBER = $SCHEMA.member("stringMember"); + public StringMemberMember { + Objects.requireNonNull(stringMember, "Union value cannot be null"); + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeStruct($SCHEMA_STRING_MEMBER, stringMember); + } + + @Override + @SuppressWarnings("unchecked") + public StringEvent getValue() { + return stringMember; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + } + + record $Unknown(String memberName) implements EventStreamWithError { + @Override + public void serialize(ShapeSerializer serializer) { + throw new UnsupportedOperationException("Cannot serialize union with unknown member " + this.memberName); + } + + @Override + public void serializeMembers(ShapeSerializer serializer) {} + + @Override + @SuppressWarnings("unchecked") + public String getValue() { + return memberName; + } + + private record $Hidden() implements EventStreamWithError { + @Override + public void serializeMembers(ShapeSerializer serializer) {} + + @Override + @SuppressWarnings("unchecked") + public T getValue() { + return null; + } + } + } + + interface BuildStage { + EventStreamWithError build(); + } + + /** + * @return returns a new Builder. + */ + static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link EventStreamWithError}. + */ + final class Builder implements ShapeBuilder, BuildStage { + private EventStreamWithError value; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + public BuildStage modeledErrorMember(MyError value) { + return setValue(new ModeledErrorMemberMember(value)); + } + + public BuildStage stringMember(StringEvent value) { + return setValue(new StringMemberMember(value)); + } + + public BuildStage $unknownMember(String memberName) { + return setValue(new $Unknown(memberName)); + } + + private BuildStage setValue(EventStreamWithError value) { + if (this.value != null) { + throw new IllegalArgumentException("Only one value may be set for unions"); + } + this.value = value; + return this; + } + + @Override + public EventStreamWithError build() { + return Objects.requireNonNull(value, "no union value set"); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> modeledErrorMember((MyError) SchemaUtils + .validateSameMember(ModeledErrorMemberMember.$SCHEMA_MODELED_ERROR_MEMBER, member, value)); + case 1 -> stringMember((StringEvent) SchemaUtils + .validateSameMember(StringMemberMember.$SCHEMA_STRING_MEMBER, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + @SuppressWarnings("unchecked") + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.modeledErrorMember(MyError.builder().deserializeMember(de, member).build()); + case 1 -> builder.stringMember(StringEvent.builder().deserializeMember(de, member).build()); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + + @Override + public void unknownMember(Builder builder, String memberName) { + builder.$unknownMember(memberName); + } + } + } +} diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamingTestServiceApiService.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamingTestServiceApiService.java index e15375dce..573a74d34 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamingTestServiceApiService.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/EventStreamingTestServiceApiService.java @@ -17,7 +17,7 @@ public final class EventStreamingTestServiceApiService implements ApiService { private static final EventStreamingTestServiceApiService $INSTANCE = new EventStreamingTestServiceApiService(); private static final Schema $SCHEMA = - Schema.createService(ShapeId.from("smithy.test.eventstreaming#EventStreamingTestService")); + Schema.createService(ShapeId.from("smithy.example.eventstreaming#EventStreamingTestService")); /** * Get an instance of this {@code ApiService}. diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/GeneratedSchemaIndex.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/GeneratedSchemaIndex.java index d9422f228..8ed4139c7 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/GeneratedSchemaIndex.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/GeneratedSchemaIndex.java @@ -28,6 +28,10 @@ public final class GeneratedSchemaIndex extends SchemaIndex { SCHEMA_MAP.put(Schemas.TEST_EVENT_STREAM.id(), Schemas.TEST_EVENT_STREAM); SCHEMA_MAP.put(Schemas.TEST_OPERATION_INPUT.id(), Schemas.TEST_OPERATION_INPUT); SCHEMA_MAP.put(Schemas.TEST_OPERATION_OUTPUT.id(), Schemas.TEST_OPERATION_OUTPUT); + SCHEMA_MAP.put(Schemas.TEST_OPERATION_WITH_EXCEPTION_INPUT.id(), Schemas.TEST_OPERATION_WITH_EXCEPTION_INPUT); + SCHEMA_MAP.put(Schemas.MY_ERROR.id(), Schemas.MY_ERROR); + SCHEMA_MAP.put(Schemas.EVENT_STREAM_WITH_ERROR.id(), Schemas.EVENT_STREAM_WITH_ERROR); + SCHEMA_MAP.put(Schemas.TEST_OPERATION_WITH_EXCEPTION_OUTPUT.id(), Schemas.TEST_OPERATION_WITH_EXCEPTION_OUTPUT); } @Override diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/HeadersOnlyEvent.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/HeadersOnlyEvent.java index b1d8d2475..a2fc337b1 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/HeadersOnlyEvent.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/HeadersOnlyEvent.java @@ -148,6 +148,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.sequenceNum(de.readInteger(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/MyError.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/MyError.java new file mode 100644 index 000000000..019faff99 --- /dev/null +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/MyError.java @@ -0,0 +1,155 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.events.model; + +import software.amazon.smithy.java.core.error.ErrorFault; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class MyError extends EventStreamingTestServiceException { + + public static final Schema $SCHEMA = Schemas.MY_ERROR; + private static final Schema $SCHEMA_MESSAGE = $SCHEMA.member("message"); + + public static final ShapeId $ID = $SCHEMA.id(); + + private MyError(Builder builder) { + super($SCHEMA, + builder.message, + builder.$cause, + ErrorFault.CLIENT, + builder.$captureStackTrace, + builder.$deserialized); + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + if (getMessage() != null) { + serializer.writeString($SCHEMA_MESSAGE, getMessage()); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_MESSAGE, member, getMessage()); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link MyError}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.message(getMessage()); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link MyError}. + */ + public static final class Builder implements ShapeBuilder { + private String message; + private Throwable $cause; + private Boolean $captureStackTrace; + private boolean $deserialized; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + * @return this builder. + */ + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder withStackTrace() { + this.$captureStackTrace = true; + return this; + } + + public Builder withoutStackTrace() { + this.$captureStackTrace = false; + return this; + } + + public Builder withCause(Throwable cause) { + this.$cause = cause; + return this; + } + + @Override + public MyError build() { + return new MyError(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> message((String) SchemaUtils.validateSameMember($SCHEMA_MESSAGE, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + this.$deserialized = true; + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + @SuppressWarnings("unchecked") + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.message(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/Schemas.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/Schemas.java index af6ce62e9..e44f904b5 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/Schemas.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/Schemas.java @@ -8,6 +8,7 @@ import software.amazon.smithy.java.core.schema.PreludeSchemas; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.model.shapes.ShapeId; +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.StreamingTrait; @@ -16,7 +17,7 @@ * Defines schemas for shapes in the model package. */ final class Schemas { - static final Schema BLOB_EVENT = Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#BlobEvent")) + static final Schema BLOB_EVENT = Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#BlobEvent")) .putMember("payload", PreludeSchemas.BLOB, new EventPayloadTrait()) @@ -24,7 +25,7 @@ final class Schemas { .build(); static final Schema BODY_AND_HEADER_EVENT = - Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#BodyAndHeaderEvent")) + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#BodyAndHeaderEvent")) .putMember("intMember", PreludeSchemas.INTEGER, new EventHeaderTrait()) @@ -33,28 +34,29 @@ final class Schemas { .build(); static final Schema HEADERS_ONLY_EVENT = - Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#HeadersOnlyEvent")) + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#HeadersOnlyEvent")) .putMember("sequenceNum", PreludeSchemas.INTEGER, new EventHeaderTrait()) .builderSupplier(HeadersOnlyEvent::builder) .build(); - static final Schema STRING_EVENT = Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#StringEvent")) - .putMember("payload", - PreludeSchemas.STRING, - new EventPayloadTrait()) - .builderSupplier(StringEvent::builder) - .build(); + static final Schema STRING_EVENT = + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#StringEvent")) + .putMember("payload", + PreludeSchemas.STRING, + new EventPayloadTrait()) + .builderSupplier(StringEvent::builder) + .build(); static final Schema STRUCTURE_EVENT = - Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#StructureEvent")) + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#StructureEvent")) .putMember("foo", PreludeSchemas.STRING) .builderSupplier(StructureEvent::builder) .build(); static final Schema TEST_EVENT_STREAM = Schema - .unionBuilder(ShapeId.from("smithy.test.eventstreaming#TestEventStream"), + .unionBuilder(ShapeId.from("smithy.example.eventstreaming#TestEventStream"), new StreamingTrait()) .putMember("structureMember", Schemas.STRUCTURE_EVENT) .putMember("stringMember", Schemas.STRING_EVENT) @@ -65,7 +67,7 @@ final class Schemas { .build(); static final Schema TEST_OPERATION_INPUT = - Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#TestInput")) + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#TestOperationInput")) .putMember("headerString", PreludeSchemas.STRING, new EventHeaderTrait()) @@ -75,7 +77,7 @@ final class Schemas { .build(); static final Schema TEST_OPERATION_OUTPUT = - Schema.structureBuilder(ShapeId.from("smithy.test.eventstreaming#TestOutput")) + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#TestOperationOutput")) .putMember("intMemberHeader", PreludeSchemas.INTEGER, new EventHeaderTrait()) @@ -84,5 +86,31 @@ final class Schemas { .builderSupplier(TestOperationOutput::builder) .build(); + static final Schema TEST_OPERATION_WITH_EXCEPTION_INPUT = + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#TestOperationWithExceptionInput")) + .putMember("stream", Schemas.TEST_EVENT_STREAM) + .builderSupplier(TestOperationWithExceptionInput::builder) + .build(); + + static final Schema MY_ERROR = Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#MyError"), + new ErrorTrait("client")) + .putMember("message", PreludeSchemas.STRING) + .builderSupplier(MyError::builder) + .build(); + + static final Schema EVENT_STREAM_WITH_ERROR = Schema + .unionBuilder(ShapeId.from("smithy.example.eventstreaming#EventStreamWithError"), + new StreamingTrait()) + .putMember("modeledErrorMember", Schemas.MY_ERROR) + .putMember("stringMember", Schemas.STRING_EVENT) + .builderSupplier(EventStreamWithError::builder) + .build(); + + static final Schema TEST_OPERATION_WITH_EXCEPTION_OUTPUT = + Schema.structureBuilder(ShapeId.from("smithy.example.eventstreaming#TestOperationWithExceptionOutput")) + .putMember("outputStream", Schemas.EVENT_STREAM_WITH_ERROR) + .builderSupplier(TestOperationWithExceptionOutput::builder) + .build(); + private Schemas() {} } diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StringEvent.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StringEvent.java index 201dd93f4..8764baf8e 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StringEvent.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StringEvent.java @@ -148,6 +148,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.payload(de.readString(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StructureEvent.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StructureEvent.java index e0dba37f1..4956e03ea 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StructureEvent.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/StructureEvent.java @@ -148,6 +148,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.foo(de.readString(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestEventStream.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestEventStream.java index 9e699c002..e4022bbdf 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestEventStream.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestEventStream.java @@ -17,192 +17,149 @@ import software.amazon.smithy.utils.SmithyGenerated; @SmithyGenerated -public abstract class TestEventStream implements SerializableStruct { - public static final Schema $SCHEMA = Schemas.TEST_EVENT_STREAM; - private static final Schema $SCHEMA_STRUCTURE_MEMBER = $SCHEMA.member("structureMember"); - private static final Schema $SCHEMA_STRING_MEMBER = $SCHEMA.member("stringMember"); - private static final Schema $SCHEMA_BLOB_MEMBER = $SCHEMA.member("blobMember"); - private static final Schema $SCHEMA_HEADERS_ONLY_MEMBER = $SCHEMA.member("headersOnlyMember"); - private static final Schema $SCHEMA_BODY_AND_HEADER_MEMBER = $SCHEMA.member("bodyAndHeaderMember"); +public sealed interface TestEventStream extends SerializableStruct { + Schema $SCHEMA = Schemas.TEST_EVENT_STREAM; - public static final ShapeId $ID = $SCHEMA.id(); + ShapeId $ID = $SCHEMA.id(); - private final Type type; - - private TestEventStream(Type type) { - this.type = type; - } - - public Type type() { - return type; - } - - /** - * Enum representing the possible variants of {@link TestEventStream}. - */ - public enum Type { - $UNKNOWN, - structureMember, - stringMember, - blobMember, - headersOnlyMember, - bodyAndHeaderMember - } - - @Override - public String toString() { - return ToStringSerializer.serialize(this); - } + T getValue(); @Override - public Schema schema() { + default Schema schema() { return $SCHEMA; } @Override - public T getMemberValue(Schema member) { + default T getMemberValue(Schema member) { return SchemaUtils.validateMemberInSchema($SCHEMA, member, getValue()); } - public abstract T getValue(); - @SmithyGenerated - public static final class StructureMemberMember extends TestEventStream { - private final transient StructureEvent value; - - public StructureMemberMember(StructureEvent value) { - super(Type.structureMember); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); + record StructureMemberMember(StructureEvent structureMember) implements TestEventStream { + private static final Schema $SCHEMA_STRUCTURE_MEMBER = $SCHEMA.member("structureMember"); + public StructureMemberMember { + Objects.requireNonNull(structureMember, "Union value cannot be null"); } @Override public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_STRUCTURE_MEMBER, value); + serializer.writeStruct($SCHEMA_STRUCTURE_MEMBER, structureMember); } - public StructureEvent getStructureMember() { - return value; + @Override + @SuppressWarnings("unchecked") + public StructureEvent getValue() { + return structureMember; } @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; + public String toString() { + return ToStringSerializer.serialize(this); } + } @SmithyGenerated - public static final class StringMemberMember extends TestEventStream { - private final transient StringEvent value; - - public StringMemberMember(StringEvent value) { - super(Type.stringMember); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); + record StringMemberMember(StringEvent stringMember) implements TestEventStream { + private static final Schema $SCHEMA_STRING_MEMBER = $SCHEMA.member("stringMember"); + public StringMemberMember { + Objects.requireNonNull(stringMember, "Union value cannot be null"); } @Override public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_STRING_MEMBER, value); + serializer.writeStruct($SCHEMA_STRING_MEMBER, stringMember); } - public StringEvent getStringMember() { - return value; + @Override + @SuppressWarnings("unchecked") + public StringEvent getValue() { + return stringMember; } @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; + public String toString() { + return ToStringSerializer.serialize(this); } + } @SmithyGenerated - public static final class BlobMemberMember extends TestEventStream { - private final transient BlobEvent value; - - public BlobMemberMember(BlobEvent value) { - super(Type.blobMember); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); + record BlobMemberMember(BlobEvent blobMember) implements TestEventStream { + private static final Schema $SCHEMA_BLOB_MEMBER = $SCHEMA.member("blobMember"); + public BlobMemberMember { + Objects.requireNonNull(blobMember, "Union value cannot be null"); } @Override public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_BLOB_MEMBER, value); + serializer.writeStruct($SCHEMA_BLOB_MEMBER, blobMember); } - public BlobEvent getBlobMember() { - return value; + @Override + @SuppressWarnings("unchecked") + public BlobEvent getValue() { + return blobMember; } @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; + public String toString() { + return ToStringSerializer.serialize(this); } + } @SmithyGenerated - public static final class HeadersOnlyMemberMember extends TestEventStream { - private final transient HeadersOnlyEvent value; - - public HeadersOnlyMemberMember(HeadersOnlyEvent value) { - super(Type.headersOnlyMember); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); + record HeadersOnlyMemberMember(HeadersOnlyEvent headersOnlyMember) implements TestEventStream { + private static final Schema $SCHEMA_HEADERS_ONLY_MEMBER = $SCHEMA.member("headersOnlyMember"); + public HeadersOnlyMemberMember { + Objects.requireNonNull(headersOnlyMember, "Union value cannot be null"); } @Override public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_HEADERS_ONLY_MEMBER, value); + serializer.writeStruct($SCHEMA_HEADERS_ONLY_MEMBER, headersOnlyMember); } - public HeadersOnlyEvent getHeadersOnlyMember() { - return value; + @Override + @SuppressWarnings("unchecked") + public HeadersOnlyEvent getValue() { + return headersOnlyMember; } @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; + public String toString() { + return ToStringSerializer.serialize(this); } + } @SmithyGenerated - public static final class BodyAndHeaderMemberMember extends TestEventStream { - private final transient BodyAndHeaderEvent value; - - public BodyAndHeaderMemberMember(BodyAndHeaderEvent value) { - super(Type.bodyAndHeaderMember); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); + record BodyAndHeaderMemberMember(BodyAndHeaderEvent bodyAndHeaderMember) implements TestEventStream { + private static final Schema $SCHEMA_BODY_AND_HEADER_MEMBER = $SCHEMA.member("bodyAndHeaderMember"); + public BodyAndHeaderMemberMember { + Objects.requireNonNull(bodyAndHeaderMember, "Union value cannot be null"); } @Override public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_BODY_AND_HEADER_MEMBER, value); - } - - public BodyAndHeaderEvent getBodyAndHeaderMember() { - return value; + serializer.writeStruct($SCHEMA_BODY_AND_HEADER_MEMBER, bodyAndHeaderMember); } @Override @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; + public BodyAndHeaderEvent getValue() { + return bodyAndHeaderMember; } - } - public static final class $UnknownMember extends TestEventStream { - private final String memberName; - - public $UnknownMember(String memberName) { - super(Type.$UNKNOWN); - this.memberName = memberName; + @Override + public String toString() { + return ToStringSerializer.serialize(this); } - public String memberName() { - return memberName; - } + } + record $Unknown(String memberName) implements TestEventStream { @Override public void serialize(ShapeSerializer serializer) { throw new UnsupportedOperationException("Cannot serialize union with unknown member " + this.memberName); @@ -213,42 +170,37 @@ public void serializeMembers(ShapeSerializer serializer) {} @Override @SuppressWarnings("unchecked") - public T getValue() { - return (T) memberName; + public String getValue() { + return memberName; } - } - @Override - public int hashCode() { - return Objects.hash(type, getValue()); - } + private record $Hidden() implements TestEventStream { + @Override + public void serializeMembers(ShapeSerializer serializer) {} - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; + @Override + @SuppressWarnings("unchecked") + public T getValue() { + return null; + } } - return Objects.equals(getValue(), ((TestEventStream) other).getValue()); } - public interface BuildStage { + interface BuildStage { TestEventStream build(); } /** * @return returns a new Builder. */ - public static Builder builder() { + static Builder builder() { return new Builder(); } /** * Builder for {@link TestEventStream}. */ - public static final class Builder implements ShapeBuilder, BuildStage { + final class Builder implements ShapeBuilder, BuildStage { private TestEventStream value; private Builder() {} @@ -279,14 +231,11 @@ public BuildStage bodyAndHeaderMember(BodyAndHeaderEvent value) { } public BuildStage $unknownMember(String memberName) { - return setValue(new $UnknownMember(memberName)); + return setValue(new $Unknown(memberName)); } private BuildStage setValue(TestEventStream value) { if (this.value != null) { - if (this.value.type() == Type.$UNKNOWN) { - throw new IllegalArgumentException("Cannot change union from unknown to known variant"); - } throw new IllegalArgumentException("Only one value may be set for unions"); } this.value = value; @@ -302,15 +251,16 @@ public TestEventStream build() { @SuppressWarnings("unchecked") public void setMemberValue(Schema member, Object value) { switch (member.memberIndex()) { - case 0 -> structureMember( - (StructureEvent) SchemaUtils.validateSameMember($SCHEMA_STRUCTURE_MEMBER, member, value)); - case 1 -> - stringMember((StringEvent) SchemaUtils.validateSameMember($SCHEMA_STRING_MEMBER, member, value)); - case 2 -> blobMember((BlobEvent) SchemaUtils.validateSameMember($SCHEMA_BLOB_MEMBER, member, value)); - case 3 -> headersOnlyMember( - (HeadersOnlyEvent) SchemaUtils.validateSameMember($SCHEMA_HEADERS_ONLY_MEMBER, member, value)); + case 0 -> structureMember((StructureEvent) SchemaUtils + .validateSameMember(StructureMemberMember.$SCHEMA_STRUCTURE_MEMBER, member, value)); + case 1 -> stringMember((StringEvent) SchemaUtils + .validateSameMember(StringMemberMember.$SCHEMA_STRING_MEMBER, member, value)); + case 2 -> blobMember((BlobEvent) SchemaUtils + .validateSameMember(BlobMemberMember.$SCHEMA_BLOB_MEMBER, member, value)); + case 3 -> headersOnlyMember((HeadersOnlyEvent) SchemaUtils + .validateSameMember(HeadersOnlyMemberMember.$SCHEMA_HEADERS_ONLY_MEMBER, member, value)); case 4 -> bodyAndHeaderMember((BodyAndHeaderEvent) SchemaUtils - .validateSameMember($SCHEMA_BODY_AND_HEADER_MEMBER, member, value)); + .validateSameMember(BodyAndHeaderMemberMember.$SCHEMA_BODY_AND_HEADER_MEMBER, member, value)); default -> ShapeBuilder.super.setMemberValue(member, value); } } @@ -331,6 +281,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.structureMember(StructureEvent.builder().deserializeMember(de, member).build()); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperation.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperation.java index e128f3791..877e60273 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperation.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperation.java @@ -17,12 +17,11 @@ import software.amazon.smithy.utils.SmithyGenerated; @SmithyGenerated -public final class TestOperation - implements ApiOperation { +public final class TestOperation implements ApiOperation { private static final TestOperation $INSTANCE = new TestOperation(); - static final Schema $SCHEMA = Schema.createOperation(ShapeId.from("smithy.test.eventstreaming#TestOperation")); + static final Schema $SCHEMA = Schema.createOperation(ShapeId.from("smithy.example.eventstreaming#TestOperation")); public static final ShapeId $ID = $SCHEMA.id(); @@ -84,6 +83,11 @@ public TypeRegistry errorRegistry() { return TYPE_REGISTRY; } + @Override + public List errorSchemas() { + return List.of(); + } + @Override public List effectiveAuthSchemes() { return SCHEMES; @@ -99,11 +103,6 @@ public Schema outputStreamMember() { return OUTPUT_STREAM_MEMBER; } - @Override - public List errorSchemas() { - return List.of(); - } - @Override public Schema idempotencyTokenMember() { return null; diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationInput.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationInput.java index 787f29eb9..e72a94b4a 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationInput.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationInput.java @@ -175,9 +175,8 @@ public void setMemberValue(Schema member, Object value) { case 0 -> headerString((String) SchemaUtils.validateSameMember($SCHEMA_HEADER_STRING, member, value)); case 1 -> inputStringMember( (String) SchemaUtils.validateSameMember($SCHEMA_INPUT_STRING_MEMBER, member, value)); - case 2 -> - stream((EventStream) SchemaUtils - .validateSameMember($SCHEMA_STREAM, member, value)); + case 2 -> stream( + (EventStream) SchemaUtils.validateSameMember($SCHEMA_STREAM, member, value)); default -> ShapeBuilder.super.setMemberValue(member, value); } } @@ -198,6 +197,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.headerString(de.readString(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationOutput.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationOutput.java index 3ddd4f1d1..6c7e41694 100644 --- a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationOutput.java +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationOutput.java @@ -197,6 +197,7 @@ private static final class $InnerDeserializer implements ShapeDeserializer.Struc private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); @Override + @SuppressWarnings("unchecked") public void accept(Builder builder, Schema member, ShapeDeserializer de) { switch (member.memberIndex()) { case 0 -> builder.intMemberHeader(de.readInteger(member)); diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithException.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithException.java new file mode 100644 index 000000000..b319ae2fb --- /dev/null +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithException.java @@ -0,0 +1,117 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.events.model; + +import java.util.List; +import java.util.function.Supplier; +import software.amazon.smithy.java.core.schema.ApiOperation; +import software.amazon.smithy.java.core.schema.ApiService; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.TypeRegistry; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class TestOperationWithException + implements ApiOperation { + + private static final TestOperationWithException $INSTANCE = new TestOperationWithException(); + + static final Schema $SCHEMA = + Schema.createOperation(ShapeId.from("smithy.example.eventstreaming#TestOperationWithException")); + + public static final ShapeId $ID = $SCHEMA.id(); + + private static final TypeRegistry TYPE_REGISTRY = TypeRegistry.empty(); + + private static final List SCHEMES = List.of(ShapeId.from("smithy.api#noAuth")); + + private static final Schema INPUT_STREAM_MEMBER = TestOperationWithExceptionInput.$SCHEMA.member("stream"); + private static final Schema OUTPUT_STREAM_MEMBER = TestOperationWithExceptionOutput.$SCHEMA.member("outputStream"); + + /** + * Get an instance of this {@code ApiOperation}. + * + * @return An instance of this class. + */ + public static TestOperationWithException instance() { + return $INSTANCE; + } + + private TestOperationWithException() {} + + @Override + public ShapeBuilder inputBuilder() { + return TestOperationWithExceptionInput.builder(); + } + + @Override + public Supplier> inputEventBuilderSupplier() { + return () -> TestEventStream.builder(); + } + + @Override + public ShapeBuilder outputBuilder() { + return TestOperationWithExceptionOutput.builder(); + } + + @Override + public Supplier> outputEventBuilderSupplier() { + return () -> EventStreamWithError.builder(); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public Schema inputSchema() { + return TestOperationWithExceptionInput.$SCHEMA; + } + + @Override + public Schema outputSchema() { + return TestOperationWithExceptionOutput.$SCHEMA; + } + + @Override + public TypeRegistry errorRegistry() { + return TYPE_REGISTRY; + } + + @Override + public List errorSchemas() { + return List.of(); + } + + @Override + public List effectiveAuthSchemes() { + return SCHEMES; + } + + @Override + public Schema inputStreamMember() { + return INPUT_STREAM_MEMBER; + } + + @Override + public Schema outputStreamMember() { + return OUTPUT_STREAM_MEMBER; + } + + @Override + public Schema idempotencyTokenMember() { + return null; + } + + @Override + public ApiService service() { + return EventStreamingTestServiceApiService.instance(); + } +} diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionInput.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionInput.java new file mode 100644 index 000000000..7ffabd03a --- /dev/null +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionInput.java @@ -0,0 +1,162 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.events.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.java.core.serde.event.EventStream; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class TestOperationWithExceptionInput implements SerializableStruct { + + public static final Schema $SCHEMA = Schemas.TEST_OPERATION_WITH_EXCEPTION_INPUT; + private static final Schema $SCHEMA_STREAM = $SCHEMA.member("stream"); + + public static final ShapeId $ID = $SCHEMA.id(); + + private final transient EventStream stream; + + private TestOperationWithExceptionInput(Builder builder) { + this.stream = builder.stream; + } + + public EventStream getStream() { + return stream; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + TestOperationWithExceptionInput that = (TestOperationWithExceptionInput) other; + return Objects.equals(this.stream, that.stream); + } + + @Override + public int hashCode() { + return Objects.hash(stream); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + if (stream != null) { + serializer.writeEventStream($SCHEMA_STREAM, stream); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_STREAM, member, stream); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link TestOperationWithExceptionInput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.stream(this.stream); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link TestOperationWithExceptionInput}. + */ + public static final class Builder implements ShapeBuilder { + private EventStream stream; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + * @return this builder. + */ + public Builder stream(EventStream stream) { + this.stream = stream; + return this; + } + + @Override + public TestOperationWithExceptionInput build() { + return new TestOperationWithExceptionInput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> stream( + (EventStream) SchemaUtils.validateSameMember($SCHEMA_STREAM, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + @SuppressWarnings("unchecked") + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.stream((EventStream) de.readEventStream(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} diff --git a/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionOutput.java b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionOutput.java new file mode 100644 index 000000000..162cbe604 --- /dev/null +++ b/aws/aws-event-streams/src/test/java/software/amazon/smithy/java/aws/events/model/TestOperationWithExceptionOutput.java @@ -0,0 +1,162 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.events.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.java.core.serde.event.EventStream; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class TestOperationWithExceptionOutput implements SerializableStruct { + + public static final Schema $SCHEMA = Schemas.TEST_OPERATION_WITH_EXCEPTION_OUTPUT; + private static final Schema $SCHEMA_OUTPUT_STREAM = $SCHEMA.member("outputStream"); + + public static final ShapeId $ID = $SCHEMA.id(); + + private final transient EventStream outputStream; + + private TestOperationWithExceptionOutput(Builder builder) { + this.outputStream = builder.outputStream; + } + + public EventStream getOutputStream() { + return outputStream; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + TestOperationWithExceptionOutput that = (TestOperationWithExceptionOutput) other; + return Objects.equals(this.outputStream, that.outputStream); + } + + @Override + public int hashCode() { + return Objects.hash(outputStream); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + if (outputStream != null) { + serializer.writeEventStream($SCHEMA_OUTPUT_STREAM, outputStream); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_OUTPUT_STREAM, member, outputStream); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link TestOperationWithExceptionOutput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.outputStream(this.outputStream); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link TestOperationWithExceptionOutput}. + */ + public static final class Builder implements ShapeBuilder { + private EventStream outputStream; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + * @return this builder. + */ + public Builder outputStream(EventStream outputStream) { + this.outputStream = outputStream; + return this; + } + + @Override + public TestOperationWithExceptionOutput build() { + return new TestOperationWithExceptionOutput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> outputStream((EventStream) SchemaUtils + .validateSameMember($SCHEMA_OUTPUT_STREAM, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + @SuppressWarnings("unchecked") + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.outputStream((EventStream) de.readEventStream(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} From dc7266ac2f4d1d298ddabe68bbd529342b54c487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Mon, 9 Mar 2026 11:41:40 -0700 Subject: [PATCH 2/4] Address review comments --- .../java/aws/events/AwsEventShapeDecoder.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java index f285506f2..066730228 100644 --- a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java +++ b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java @@ -60,15 +60,20 @@ public SerializableStruct decode(AwsEventFrame frame) { private E decodeEvent(AwsEventFrame frame) { var message = frame.unwrap(); var messageType = getMessageType(message); - if ("error".equals(messageType)) { - decodeErrorAndThrow(message); - } else if ("exception".equals(messageType)) { - return decodeModeledException(message); - } else if (!"event".equals(messageType)) { - throw new IllegalArgumentException("Invalid message type: " + messageType); - } - var eventType = getEventType(message); - return decodePayload(eventType, message); + switch (messageType) { + case "error" -> { + decodeErrorAndThrow(message); + return null; + } + case "event" -> { + var eventType = getEventType(message); + return decodePayload(eventType, message); + } + case "exception" -> { + return decodeModeledException(message); + } + default -> throw new IllegalStateException(String.format("Unknown message type: %s", messageType)); + } } private E decodeModeledException(Message message) { @@ -98,9 +103,15 @@ private E decodePayload(String eventType, Message message) { private void decodeErrorAndThrow(Message message) { var errorCodeHeader = message.getHeaders().get(":error-code"); - var errorCode = errorCodeHeader != null ? errorCodeHeader.getString() : "unknown error code"; + if (errorCodeHeader == null) { + throw new IllegalArgumentException("expected headers to have ':error-code' header"); + } + var errorCode = errorCodeHeader.getString(); var errorMessageHeder = message.getHeaders().get(":error-message"); - var errorMessage = errorMessageHeder != null ? errorMessageHeder.getString() : "unknown error message"; + if (errorMessageHeder == null) { + throw new IllegalArgumentException("expected headers to have ':error-message' header"); + } + var errorMessage = errorMessageHeder.getString(); throw new EventStreamingException(errorCode, errorMessage); } From 4806ffc3d61ccb73b64f5560214ae2c6a412a240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Mon, 9 Mar 2026 13:16:36 -0700 Subject: [PATCH 3/4] Address review comments --- .../java/aws/events/AwsEventShapeDecoder.java | 33 ++++++++----------- .../serde/event/EventStreamingException.java | 16 +++++++-- .../EventStreamingProtocolException.java | 20 +++++++++++ 3 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingProtocolException.java diff --git a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java index 066730228..228ca903f 100644 --- a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java +++ b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java @@ -22,6 +22,7 @@ import software.amazon.smithy.java.core.serde.event.EventDecoder; import software.amazon.smithy.java.core.serde.event.EventStream; import software.amazon.smithy.java.core.serde.event.EventStreamingException; +import software.amazon.smithy.java.core.serde.event.EventStreamingProtocolException; /** * A decoder for AWS events @@ -60,26 +61,18 @@ public SerializableStruct decode(AwsEventFrame frame) { private E decodeEvent(AwsEventFrame frame) { var message = frame.unwrap(); var messageType = getMessageType(message); - switch (messageType) { - case "error" -> { - decodeErrorAndThrow(message); - return null; - } - case "event" -> { - var eventType = getEventType(message); - return decodePayload(eventType, message); - } - case "exception" -> { - return decodeModeledException(message); - } - default -> throw new IllegalStateException(String.format("Unknown message type: %s", messageType)); - } + return switch (messageType) { + case "error" -> throw decodeError(message); + case "event" -> decodePayload(getEventType(message), message); + case "exception" -> decodeModeledException(message); + default -> throw new IllegalStateException("Unknown message type: " + messageType); + }; } private E decodeModeledException(Message message) { var exceptionTypeHeader = message.getHeaders().get(":exception-type"); if (exceptionTypeHeader == null) { - throw new IllegalStateException("expected headers to have ':exception-type' header"); + throw new EventStreamingProtocolException("expected headers to have ':exception-type' header"); } var exceptionType = exceptionTypeHeader.getString(); return decodePayload(exceptionType, message); @@ -88,7 +81,7 @@ private E decodeModeledException(Message message) { private E decodePayload(String eventType, Message message) { var memberSchema = eventSchema.member(eventType); if (memberSchema == null) { - throw new IllegalArgumentException("Unsupported event type: " + eventType); + throw new EventStreamingProtocolException("Unsupported event type: " + eventType); } var codecDeserializer = codec.createDeserializer(message.getPayload()); var headers = message.getHeaders(); @@ -101,18 +94,18 @@ private E decodePayload(String eventType, Message message) { return builder.build(); } - private void decodeErrorAndThrow(Message message) { + private EventStreamingProtocolException decodeError(Message message) { var errorCodeHeader = message.getHeaders().get(":error-code"); if (errorCodeHeader == null) { - throw new IllegalArgumentException("expected headers to have ':error-code' header"); + return new EventStreamingProtocolException("expected headers to have ':error-code' header"); } var errorCode = errorCodeHeader.getString(); var errorMessageHeder = message.getHeaders().get(":error-message"); if (errorMessageHeder == null) { - throw new IllegalArgumentException("expected headers to have ':error-message' header"); + return new EventStreamingProtocolException("expected headers to have ':error-message' header"); } var errorMessage = errorMessageHeder.getString(); - throw new EventStreamingException(errorCode, errorMessage); + return new EventStreamingException(errorCode, errorMessage); } @Override diff --git a/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingException.java b/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingException.java index 5d6f38666..0354fad15 100644 --- a/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingException.java +++ b/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingException.java @@ -5,15 +5,25 @@ package software.amazon.smithy.java.core.serde.event; -public class EventStreamingException extends RuntimeException { +import java.util.Objects; + +/** + * Exception thrown upon receiving events of type :message-type exception. + */ +public final class EventStreamingException extends EventStreamingProtocolException { private final String errorCode; public EventStreamingException(String errorCode, String errorMessage) { - super(errorMessage); - this.errorCode = errorCode; + super(Objects.requireNonNull(errorMessage, "errorMessage")); + this.errorCode = Objects.requireNonNull(errorCode, "errorCode"); } + /** + * Returns the error code of the exception. + * + * @return the error code of the exception + */ public String getErrorCode() { return errorCode; } diff --git a/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingProtocolException.java b/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingProtocolException.java new file mode 100644 index 000000000..6a7fbf1b8 --- /dev/null +++ b/core/src/main/java/software/amazon/smithy/java/core/serde/event/EventStreamingProtocolException.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.core.serde.event; + +/** + * Generic exception thrown in event streaming. + */ +public class EventStreamingProtocolException extends RuntimeException { + /** + * Creates a new exception. + * + * @param message the message. + */ + public EventStreamingProtocolException(String message) { + super(message); + } +} From 37d08434e9bfaf07dceff1fa7b7eeab032260d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Sugawara=20=28=E2=88=A9=EF=BD=80-=C2=B4=29?= =?UTF-8?q?=E2=8A=83=E2=94=81=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E=E7=82=8E?= Date: Mon, 9 Mar 2026 15:55:36 -0700 Subject: [PATCH 4/4] Add a new method expectHeader to avoid NPE --- .../java/aws/events/AwsEventShapeDecoder.java | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java index 228ca903f..f381109a2 100644 --- a/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java +++ b/aws/aws-event-streams/src/main/java/software/amazon/smithy/java/aws/events/AwsEventShapeDecoder.java @@ -70,11 +70,7 @@ private E decodeEvent(AwsEventFrame frame) { } private E decodeModeledException(Message message) { - var exceptionTypeHeader = message.getHeaders().get(":exception-type"); - if (exceptionTypeHeader == null) { - throw new EventStreamingProtocolException("expected headers to have ':exception-type' header"); - } - var exceptionType = exceptionTypeHeader.getString(); + var exceptionType = expectHeader(message, ":exception-type").getString(); return decodePayload(exceptionType, message); } @@ -95,16 +91,8 @@ private E decodePayload(String eventType, Message message) { } private EventStreamingProtocolException decodeError(Message message) { - var errorCodeHeader = message.getHeaders().get(":error-code"); - if (errorCodeHeader == null) { - return new EventStreamingProtocolException("expected headers to have ':error-code' header"); - } - var errorCode = errorCodeHeader.getString(); - var errorMessageHeder = message.getHeaders().get(":error-message"); - if (errorMessageHeder == null) { - return new EventStreamingProtocolException("expected headers to have ':error-message' header"); - } - var errorMessage = errorMessageHeder.getString(); + var errorCode = expectHeader(message, ":error-code").getString(); + var errorMessage = expectHeader(message, ":error-message").getString(); return new EventStreamingException(errorCode, errorMessage); } @@ -117,8 +105,8 @@ public IR decodeInitialEvent(AwsEventFrame frame, EventStream eventStream) { var responseDeserializer = new InitialResponseDeserializer(publisherMember, eventStream); builder.deserialize(responseDeserializer); // Deserialize the rest of the members if any - var headers = message.getHeaders(); var codecDeserializer = codec.createDeserializer(message.getPayload()); + var headers = message.getHeaders(); var deserializer = new EventStreamDeserializer(codecDeserializer, new HeadersDeserializer(headers)); builder.deserialize(deserializer); return builder.build(); @@ -134,11 +122,19 @@ private Schema getEventStreamMember(Schema schema) { } private String getMessageType(Message message) { - return message.getHeaders().get(":message-type").getString(); + return expectHeader(message, ":message-type").getString(); } private String getEventType(Message message) { - return message.getHeaders().get(":event-type").getString(); + return expectHeader(message, ":event-type").getString(); + } + + private HeaderValue expectHeader(Message message, String headerName) { + var header = message.getHeaders().get(headerName); + if (header == null) { + throw new EventStreamingProtocolException("expected headers to have '" + headerName + "' header"); + } + return header; } static class InitialResponseDeserializer extends SpecificShapeDeserializer {