diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java
index 06a8c3dab0e..1073b9b8b62 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/HttpBindingIndex.java
@@ -32,6 +32,7 @@
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.ToShapeId;
import software.amazon.smithy.model.traits.ErrorTrait;
+import software.amazon.smithy.model.traits.EventStreamTrait;
import software.amazon.smithy.model.traits.HttpErrorTrait;
import software.amazon.smithy.model.traits.HttpHeaderTrait;
import software.amazon.smithy.model.traits.HttpLabelTrait;
@@ -252,6 +253,20 @@ public TimestampFormatTrait.Format determineTimestampFormat(
});
}
+ /**
+ * Returns the expected request Content-Type of the given operation.
+ *
+ *
See {@link #determineRequestContentType(ToShapeId, String, String)}
+ * for documentation on how the content-type is resolved.
+ *
+ * @param operation Operation to determine the content-type of.
+ * @param documentContentType Content-Type to use for protocol documents.
+ * @return Returns the optionally resolved content-type of the request.
+ */
+ public Optional determineRequestContentType(ToShapeId operation, String documentContentType) {
+ return determineRequestContentType(operation, documentContentType, null);
+ }
+
/**
* Returns the expected request Content-Type of the given operation.
*
@@ -260,14 +275,16 @@ public TimestampFormatTrait.Format determineTimestampFormat(
* to the payload, then the following checks are made:
*
*
+ * - If the payload has the {@link EventStreamTrait}, then the
+ * {@code eventStreamContentType} is returned.
+ * - If the targeted shape is a structure or document type, then
+ * the {@code documentContentType} is returned.
* - If the targeted shape has the {@link MediaTypeTrait}, then
* the value of the trait is returned.
* - If the targeted shape is a blob, then "application/octet-stream"
* is returned.
* - If the targeted shape is a string, then "text/plain" is
* returned.
- * - If the targeted shape is a structure or document type, then
- * the {@code documentContentType} is returned.
*
*
* If no members are sent in the payload, an empty Optional is
@@ -275,11 +292,31 @@ public TimestampFormatTrait.Format determineTimestampFormat(
*
* @param operation Operation to determine the content-type of.
* @param documentContentType Content-Type to use for protocol documents.
+ * @param eventStreamContentType Content-Type to use for event streams.
* @return Returns the optionally resolved content-type of the request.
*/
- public Optional determineRequestContentType(ToShapeId operation, String documentContentType) {
- String contentType = determineContentType(getRequestBindings(operation).values(), documentContentType);
- return Optional.ofNullable(contentType);
+ public Optional determineRequestContentType(
+ ToShapeId operation,
+ String documentContentType,
+ String eventStreamContentType
+ ) {
+ Collection bindings = getRequestBindings(operation).values();
+ return Optional.ofNullable(determineContentType(bindings, documentContentType, eventStreamContentType));
+ }
+
+ /**
+ * Returns the expected response Content-Type of the given operation
+ * or error.
+ *
+ * See {@link #determineResponseContentType(ToShapeId, String, String)}
+ * for documentation on how the content-type is resolved.
+ *
+ * @param operationOrError Operation or error to determine the content-type of.
+ * @param documentContentType Content-Type to use for protocol documents.
+ * @return Returns the optionally resolved content-type of the response.
+ */
+ public Optional determineResponseContentType(ToShapeId operationOrError, String documentContentType) {
+ return determineResponseContentType(operationOrError, documentContentType, null);
}
/**
@@ -291,14 +328,16 @@ public Optional determineRequestContentType(ToShapeId operation, String
* to the payload, then the following checks are made:
*
*
+ * - If the payload has the {@link EventStreamTrait}, then the
+ * {@code eventStreamContentType} is returned.
+ * - If the targeted shape is a structure or document type, then
+ * the {@code documentContentType} is returned.
* - If the targeted shape has the {@link MediaTypeTrait}, then
* the value of the trait is returned.
* - If the targeted shape is a blob, then "application/octet-stream"
* is returned.
* - If the targeted shape is a string, then "text/plain" is
* returned.
- * - If the targeted shape is a structure or document type, then
- * the {@code documentContentType} is returned.
*
*
* If no members are sent in the payload, an empty Optional is
@@ -306,35 +345,48 @@ public Optional determineRequestContentType(ToShapeId operation, String
*
* @param operationOrError Operation or error to determine the content-type of.
* @param documentContentType Content-Type to use for protocol documents.
+ * @param eventStreamContentType Content-Type used for event streams.
* @return Returns the optionally resolved content-type of the response.
*/
- public Optional determineResponseContentType(ToShapeId operationOrError, String documentContentType) {
- String contentType = determineContentType(getResponseBindings(operationOrError).values(), documentContentType);
- return Optional.ofNullable(contentType);
+ public Optional determineResponseContentType(
+ ToShapeId operationOrError,
+ String documentContentType,
+ String eventStreamContentType
+ ) {
+ Collection bindings = getResponseBindings(operationOrError).values();
+ return Optional.ofNullable(determineContentType(bindings, documentContentType, eventStreamContentType));
}
- private String determineContentType(Collection bindings, String documentContentType) {
+ private String determineContentType(
+ Collection bindings,
+ String documentContentType,
+ String eventStreamContentType
+ ) {
for (HttpBinding binding : bindings) {
if (binding.getLocation() == HttpBinding.Location.DOCUMENT) {
return documentContentType;
}
if (binding.getLocation() == HttpBinding.Location.PAYLOAD) {
- Shape target = model.getShape(binding.getMember().getTarget()).orElse(null);
+ if (binding.getMember().hasTrait(EventStreamTrait.class)) {
+ return eventStreamContentType;
+ }
+ Shape target = model.getShape(binding.getMember().getTarget()).orElse(null);
if (target == null) {
+ // Can't determine the content-type because the model is broken :(
+ // Let other parts of the validation system point this out.
break;
- }
-
- // Use the @mediaType trait if available.
- if (target.getTrait(MediaTypeTrait.class).isPresent()) {
+ } else if (target.isDocumentShape() || target.isStructureShape()) {
+ // Document type and structure targets are always the document content-type.
+ return documentContentType;
+ } else if (target.getTrait(MediaTypeTrait.class).isPresent()) {
+ // Use the @mediaType trait if available.
return target.getTrait(MediaTypeTrait.class).get().getValue();
} else if (target.isBlobShape()) {
return "application/octet-stream";
} else if (target.isStringShape()) {
return "text/plain";
- } else if (target.isDocumentShape() || target.isStructureShape()) {
- return documentContentType;
}
}
}
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/HttpBindingIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/HttpBindingIndexTest.java
index ac49dbbbbc2..3c98b91429c 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/HttpBindingIndexTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/HttpBindingIndexTest.java
@@ -260,7 +260,7 @@ public void resolvesBlobBodyContentType() {
}
@Test
- public void resolvesMediaTypeContentType() {
+ public void resolvesMediaType() {
HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);
ShapeId operation = ShapeId.from("ns.foo#ServiceOperationWithMediaType");
Optional contentType = index.determineResponseContentType(operation, "application/json");
@@ -268,6 +268,25 @@ public void resolvesMediaTypeContentType() {
assertThat(contentType, equalTo(Optional.of("application/xml")));
}
+ @Test
+ public void resolvesResponseEventStreamMediaType() {
+ HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);
+ ShapeId operation = ShapeId.from("ns.foo#ServiceOperationWithEventStream");
+ String expected = "application/vnd.amazon.eventstream";
+ Optional contentType = index.determineResponseContentType(operation, "ignore/me", expected);
+
+ assertThat(contentType, equalTo(Optional.of(expected)));
+ }
+
+ @Test
+ public void resolvesDocumentMediaType() {
+ HttpBindingIndex index = model.getKnowledge(HttpBindingIndex.class);
+ ShapeId operation = ShapeId.from("ns.foo#ServiceOperationExplicitMembers");
+ Optional contentType = index.determineResponseContentType(operation, "application/json");
+
+ assertThat(contentType, equalTo(Optional.of("application/json")));
+ }
+
private static MemberShape expectMember(Model model, String id) {
ShapeId shapeId = ShapeId.from(id);
return model.expectShape(shapeId).asMemberShape().get();
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/http-index.json b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/http-index.json
index 7d77eb6a86f..f17192c3466 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/http-index.json
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/http-index.json
@@ -19,6 +19,9 @@
},
{
"target": "ns.foo#WithLabels"
+ },
+ {
+ "target": "ns.foo#ServiceOperationWithEventStream"
}
]
},
@@ -288,6 +291,39 @@
"value": {
"target": "smithy.api#String"
}
+ },
+ "ns.foo#ServiceOperationWithEventStream": {
+ "type": "operation",
+ "output": {
+ "target": "ns.foo#ServiceOperationWithEventStreamOutput"
+ },
+ "traits": {
+ "smithy.api#http": {
+ "uri": "/ServiceOperationWithEventStream",
+ "method": "POST",
+ "code": 200
+ }
+ }
+ },
+ "ns.foo#ServiceOperationWithEventStreamOutput": {
+ "type": "structure",
+ "members": {
+ "events": {
+ "target": "ns.foo#EventStream",
+ "traits": {
+ "smithy.api#httpPayload": true,
+ "smithy.api#eventStream": true
+ }
+ }
+ }
+ },
+ "ns.foo#EventStream": {
+ "type": "structure",
+ "members": {
+ "foo": {
+ "target": "smithy.api#String"
+ }
+ }
}
}
}
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 051b5f0100f..9e54768cd03 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,8 +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(
- "eventStream", "eventPayload", "eventHeader", "streaming");
+ private static final Set TRAITS = SetUtils.of("endpoint", "hostLabel");
@Override
public byte getOrder() {
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
index 48b6d17c479..a7ce550caf5 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/protocols/AbstractRestProtocol.java
@@ -23,6 +23,8 @@
import java.util.Optional;
import java.util.TreeMap;
import software.amazon.smithy.jsonschema.Schema;
+import software.amazon.smithy.model.knowledge.EventStreamIndex;
+import software.amazon.smithy.model.knowledge.EventStreamInfo;
import software.amazon.smithy.model.knowledge.HttpBinding;
import software.amazon.smithy.model.knowledge.HttpBindingIndex;
import software.amazon.smithy.model.knowledge.OperationIndex;
@@ -61,6 +63,8 @@
*/
abstract class AbstractRestProtocol implements OpenApiProtocol {
+ private static final String AWS_EVENT_STREAM_CONTENT_TYPE = "application/vnd.amazon.eventstream";
+
/** The type of message being created. */
enum MessageType { REQUEST, RESPONSE, ERROR }
@@ -102,11 +106,12 @@ public Optional createOperation(Context context, OperationShape op
String uri = context.getOpenApiProtocol().getOperationUri(context, operation);
OperationObject.Builder builder = OperationObject.builder().operationId(operation.getId().getName());
HttpBindingIndex bindingIndex = context.getModel().getKnowledge(HttpBindingIndex.class);
+ EventStreamIndex eventStreamIndex = context.getModel().getKnowledge(EventStreamIndex.class);
createPathParameters(context, operation).forEach(builder::addParameter);
createQueryParameters(context, operation).forEach(builder::addParameter);
createRequestHeaderParameters(context, operation).forEach(builder::addParameter);
- createRequestBody(context, bindingIndex, operation).ifPresent(builder::requestBody);
- createResponses(context, bindingIndex, operation).forEach(builder::putResponse);
+ createRequestBody(context, bindingIndex, eventStreamIndex, operation).ifPresent(builder::requestBody);
+ createResponses(context, bindingIndex, eventStreamIndex, operation).forEach(builder::putResponse);
return Operation.create(method, uri, builder);
});
}
@@ -223,17 +228,43 @@ private Map createHeaderParameters(
private Optional createRequestBody(
Context context,
HttpBindingIndex bindingIndex,
+ EventStreamIndex eventStreamIndex,
OperationShape operation
) {
List payloadBindings = bindingIndex.getRequestBindings(
operation, HttpBinding.Location.PAYLOAD);
+
+ // Get the default media type if one cannot be resolved.
String documentMediaType = getDocumentMediaType(context, operation, MessageType.REQUEST);
- String mediaType = bindingIndex.determineRequestContentType(operation, documentMediaType).orElse(null);
+
+ // Get the event stream media type if an event stream is in use.
+ String eventStreamMediaType = eventStreamIndex.getInputInfo(operation)
+ .map(info -> getEventStreamMediaType(context, info))
+ .orElse(null);
+
+ String mediaType = bindingIndex
+ .determineRequestContentType(operation, documentMediaType, eventStreamMediaType)
+ .orElse(null);
+
return payloadBindings.isEmpty()
? createRequestDocument(mediaType, context, bindingIndex, operation)
: createRequestPayload(mediaType, context, payloadBindings.get(0));
}
+ /**
+ * Gets the media type of an event stream for the protocol.
+ *
+ * By default, this method returns the binary AWS event stream
+ * media type, {@code application/vnd.amazon.eventstream}.
+ *
+ * @param context Conversion context.
+ * @param info Event stream info to provide the media type for.
+ * @return Returns the media type of the event stream.
+ */
+ protected String getEventStreamMediaType(Context context, EventStreamInfo info) {
+ return AWS_EVENT_STREAM_CONTENT_TYPE;
+ }
+
private Optional createRequestPayload(
String mediaTypeRange,
Context context,
@@ -277,15 +308,20 @@ private Optional createRequestDocument(
private Map createResponses(
Context context,
HttpBindingIndex bindingIndex,
+ EventStreamIndex eventStreamIndex,
OperationShape operation
) {
Map result = new TreeMap<>();
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
+
operationIndex.getOutput(operation).ifPresent(output -> {
- updateResponsesMapWithResponseStatusAndObject(context, bindingIndex, operation, output, result);
+ updateResponsesMapWithResponseStatusAndObject(
+ context, bindingIndex, eventStreamIndex, operation, output, result);
});
+
for (StructureShape error : operationIndex.getErrors(operation)) {
- updateResponsesMapWithResponseStatusAndObject(context, bindingIndex, operation, error, result);
+ updateResponsesMapWithResponseStatusAndObject(
+ context, bindingIndex, eventStreamIndex, operation, error, result);
}
return result;
}
@@ -293,19 +329,22 @@ private Map createResponses(
private void updateResponsesMapWithResponseStatusAndObject(
Context context,
HttpBindingIndex bindingIndex,
+ EventStreamIndex eventStreamIndex,
OperationShape operation,
StructureShape shape,
Map responses
) {
Shape operationOrError = shape.hasTrait(ErrorTrait.class) ? shape : operation;
String statusCode = context.getOpenApiProtocol().getOperationResponseStatusCode(context, operationOrError);
- ResponseObject response = createResponse(context, bindingIndex, statusCode, operationOrError);
+ ResponseObject response = createResponse(
+ context, bindingIndex, eventStreamIndex, statusCode, operationOrError);
responses.put(statusCode, response);
}
private ResponseObject createResponse(
Context context,
HttpBindingIndex bindingIndex,
+ EventStreamIndex eventStreamIndex,
String statusCode,
Shape operationOrError
) {
@@ -313,7 +352,7 @@ private ResponseObject createResponse(
responseBuilder.description(String.format("%s %s response", operationOrError.getId().getName(), statusCode));
createResponseHeaderParameters(context, operationOrError)
.forEach((k, v) -> responseBuilder.putHeader(k, Ref.local(v)));
- addResponseContent(context, bindingIndex, responseBuilder, operationOrError);
+ addResponseContent(context, bindingIndex, eventStreamIndex, responseBuilder, operationOrError);
return responseBuilder.build();
}
@@ -329,13 +368,24 @@ private Map createResponseHeaderParameters(
private void addResponseContent(
Context context,
HttpBindingIndex bindingIndex,
+ EventStreamIndex eventStreamIndex,
ResponseObject.Builder responseBuilder,
Shape operationOrError
) {
List payloadBindings = bindingIndex.getResponseBindings(
operationOrError, HttpBinding.Location.PAYLOAD);
+
+ // Get the default media type if one cannot be resolved.
String documentMediaType = getDocumentMediaType(context, operationOrError, MessageType.RESPONSE);
- String mediaType = bindingIndex.determineResponseContentType(operationOrError, documentMediaType).orElse(null);
+
+ // Get the event stream media type if an event stream is in use.
+ String eventStreamMediaType = eventStreamIndex.getOutputInfo(operationOrError)
+ .map(info -> getEventStreamMediaType(context, info))
+ .orElse(null);
+
+ String mediaType = bindingIndex
+ .determineResponseContentType(operationOrError, documentMediaType, eventStreamMediaType)
+ .orElse(null);
if (!payloadBindings.isEmpty()) {
createResponsePayload(mediaType, context, payloadBindings.get(0), responseBuilder);
diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java
index 2a24fa2aaf9..0277dac15df 100644
--- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java
+++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java
@@ -274,4 +274,21 @@ public void mergesInSchemaDocumentExtensions() {
assertThat(result.getMember("foo"), equalTo(Optional.of(Node.from("baz"))));
}
+
+ // Streaming traits are converted to just an application/octet-stream
+ @Test
+ public void convertsStreamingService() {
+ Model model = Model.assembler()
+ .addImport(getClass().getResource("streaming-service.smithy"))
+ .discoverModels()
+ .assemble()
+ .unwrap();
+ ObjectNode result = OpenApiConverter.create()
+ .putSetting(OpenApiConstants.PROTOCOL, "aws.protocols#restJson1")
+ .convertToNode(model, ShapeId.from("smithy.example#Streaming"));
+ Node expectedNode = Node.parse(IoUtils.toUtf8String(
+ getClass().getResourceAsStream("streaming-service.openapi.json")));
+
+ Node.assertEquals(result, expectedNode);
+ }
}
diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraitsPluginTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraitsPluginTest.java
index 1f3822cadc0..00700508ea1 100644
--- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraitsPluginTest.java
+++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/mappers/UnsupportedTraitsPluginTest.java
@@ -16,7 +16,7 @@ public class UnsupportedTraitsPluginTest {
@BeforeAll
public static void before() {
model = Model.assembler()
- .addImport(UnsupportedTraitsPluginTest.class.getResource("streaming-service.smithy"))
+ .addImport(UnsupportedTraitsPluginTest.class.getResource("endpoint-service.smithy"))
.discoverModels()
.assemble()
.unwrap();
@@ -31,15 +31,15 @@ public static void after() {
public void logsWhenUnsupportedTraitsAreFound() {
OpenApiConverter.create()
.putSetting(OpenApiConstants.IGNORE_UNSUPPORTED_TRAITS, true)
- .convert(model, ShapeId.from("smithy.example#Streaming"));
+ .convert(model, ShapeId.from("smithy.example#EndpointService"));
}
@Test
public void throwsWhenUnsupportedTraitsAreFound() {
Exception thrown = Assertions.assertThrows(OpenApiException.class, () -> {
- OpenApiConverter.create().convert(model, ShapeId.from("smithy.example#Streaming"));
+ OpenApiConverter.create().convert(model, ShapeId.from("smithy.example#EndpointService"));
});
- Assertions.assertTrue(thrown.getMessage().contains("streaming"));
+ Assertions.assertTrue(thrown.getMessage().contains("endpoint"));
}
}
diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mappers/endpoint-service.smithy b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mappers/endpoint-service.smithy
new file mode 100644
index 00000000000..ba5559339ae
--- /dev/null
+++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mappers/endpoint-service.smithy
@@ -0,0 +1,13 @@
+// Endpoint traits are not currently supported.
+namespace smithy.example
+
+@aws.protocols#restJson1
+service EndpointService {
+ version: "2018-01-01",
+ operations: [EndpointOperation]
+}
+
+@http(method: "GET", uri: "/")
+@readonly
+@endpoint(hostPrefix: "prefix.")
+operation EndpointOperation {}
diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/streaming-service.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/streaming-service.openapi.json
new file mode 100644
index 00000000000..0279d578b5d
--- /dev/null
+++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/streaming-service.openapi.json
@@ -0,0 +1,27 @@
+{
+ "openapi": "3.0.2",
+ "info": {
+ "title": "Streaming",
+ "version": "2018-01-01"
+ },
+ "paths": {
+ "/": {
+ "get": {
+ "operationId": "StreamingOperation",
+ "responses": {
+ "200": {
+ "description": "StreamingOperation 200 response",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "byte"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mappers/streaming-service.smithy b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/streaming-service.smithy
similarity index 100%
rename from smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/mappers/streaming-service.smithy
rename to smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/streaming-service.smithy