Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

widen blob payload input types, add blob payload output converter #777

Merged
merged 2 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.HttpPayloadTrait;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.typescript.codegen.integration.AddSdkStreamMixinDependency;
import software.amazon.smithy.utils.SmithyUnstableApi;
Expand Down Expand Up @@ -135,10 +136,11 @@ static List<MemberShape> getBlobStreamingMembers(Model model, StructureShape sha
* Refer here for more rationales: https://github.com/aws/aws-sdk-js-v3/issues/843
*/
static void writeClientCommandStreamingInputType(
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape streamingMember
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape streamingMember,
String commandName
) {
String memberName = streamingMember.getMemberName();
String optionalSuffix = streamingMember.isRequired() ? "" : "?";
Expand All @@ -149,8 +151,8 @@ static void writeClientCommandStreamingInputType(
writer.write("$1L$2L: $3T[$1S]|string|Uint8Array|Buffer;", memberName, optionalSuffix,
containerSymbol);
});
writer.writeDocs(String.format("This interface extends from `%1$s` interface. There are more parameters than"
+ " `%2$s` defined in {@link %1$s}", containerSymbol.getName(), memberName));

writer.writeDocs("@public\n\nThe input for {@link " + commandName + "}.");
writer.write("export interface $1L extends $1LType {}", typeName);
}

Expand All @@ -160,18 +162,18 @@ static void writeClientCommandStreamingInputType(
* stream to string, buffer or WHATWG stream API.
*/
static void writeClientCommandStreamingOutputType(
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape streamingMember
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape streamingMember,
String commandName
) {
String memberName = streamingMember.getMemberName();
String optionalSuffix = streamingMember.isRequired() ? "" : "?";
writer.addImport("MetadataBearer", "__MetadataBearer", TypeScriptDependency.AWS_SDK_TYPES.packageName);
writer.addImport("SdkStream", "__SdkStream", TypeScriptDependency.AWS_SDK_TYPES.packageName);
writer.addImport("WithSdkStreamMixin", "__WithSdkStreamMixin", TypeScriptDependency.AWS_SDK_TYPES.packageName);


writer.writeDocs("@public\n\nThe output of {@link " + commandName + "}.");
writer.write(
"export interface $L extends __WithSdkStreamMixin<$T, $S>, __MetadataBearer {}",
typeName,
Expand All @@ -180,6 +182,79 @@ static void writeClientCommandStreamingOutputType(
);
}

static List<MemberShape> getBlobPayloadMembers(Model model, StructureShape shape) {
return shape.getAllMembers().values().stream()
.filter(memberShape -> {
Shape target = model.expectShape(memberShape.getTarget());
return target.isBlobShape()
&& memberShape.hasTrait(HttpPayloadTrait.class)
&& !target.hasTrait(StreamingTrait.class);
})
.collect(Collectors.toList());
}

static void writeClientCommandBlobPayloadInputType(
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape payloadMember,
String commandName
) {
String memberName = payloadMember.getMemberName();
String optionalSuffix = payloadMember.isRequired() ? "" : "?";

writer.addImport("BlobTypes", null, TypeScriptDependency.AWS_SDK_TYPES);

writer.writeDocs("@public");
writer.write(
"""
export type $LType = Omit<$T, $S> & {
$L: BlobTypes;
};
""",
typeName,
containerSymbol,
memberName,
memberName + optionalSuffix
);

writer.writeDocs("@public\n\nThe input for {@link " + commandName + "}.");
writer.write("export interface $1L extends $1LType {}", typeName);
}

static void writeClientCommandBlobPayloadOutputType(
TypeScriptWriter writer,
Symbol containerSymbol,
String typeName,
MemberShape payloadMember,
String commandName
) {
String memberName = payloadMember.getMemberName();
String optionalSuffix = payloadMember.isRequired() ? "" : "?";

writer.addImport("Uint8ArrayBlobAdapter", null, TypeScriptDependency.UTIL_STREAM);
writer.addDependency(TypeScriptDependency.UTIL_STREAM);

writer.writeDocs("@public");
writer.write(
"""
export type $LType = Omit<$T, $S> & {
$L: Uint8ArrayBlobAdapter;
};
""",
typeName,
containerSymbol,
memberName,
memberName + optionalSuffix
);

writer.writeDocs("@public\n\nThe output of {@link " + commandName + "}.");
writer.write(
"export interface $1L extends $1LType, __MetadataBearer {}",
typeName
);
}

/**
* Returns the list of function parameter key-value pairs to be written for
* provided parameters map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

package software.amazon.smithy.typescript.codegen;

import static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobPayloadMembers;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobStreamingMembers;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeClientCommandBlobPayloadInputType;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeClientCommandBlobPayloadOutputType;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeClientCommandStreamingInputType;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeClientCommandStreamingOutputType;

Expand Down Expand Up @@ -375,38 +378,58 @@ private void addInputAndOutputTypes() {
}

private void writeInputType(String typeName, Optional<StructureShape> inputShape, String commandName) {
writer.writeDocs("@public\n\nThe input for {@link " + commandName + "}.");
if (inputShape.isPresent()) {
StructureShape input = inputShape.get();
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, input);
if (blobStreamingMembers.isEmpty()) {
writer.write("export interface $L extends $T {}", typeName, symbolProvider.toSymbol(input));
List<MemberShape> blobPayloadMembers = getBlobPayloadMembers(model, input);

if (!blobStreamingMembers.isEmpty()) {
writeClientCommandStreamingInputType(
writer, symbolProvider.toSymbol(input), typeName,
blobStreamingMembers.get(0), commandName
);
} else if (!blobPayloadMembers.isEmpty()) {
writeClientCommandBlobPayloadInputType(
writer, symbolProvider.toSymbol(input), typeName,
blobPayloadMembers.get(0), commandName
);
} else {
writeClientCommandStreamingInputType(writer, symbolProvider.toSymbol(input), typeName,
blobStreamingMembers.get(0));
writer.writeDocs("@public\n\nThe input for {@link " + commandName + "}.");
writer.write("export interface $L extends $T {}", typeName, symbolProvider.toSymbol(input));
}
} else {
// If the input is non-existent, then use an empty object.
writer.writeDocs("@public\n\nThe input for {@link " + commandName + "}.");
writer.write("export interface $L {}", typeName);
}
}

private void writeOutputType(String typeName, Optional<StructureShape> outputShape, String commandName) {
writer.writeDocs("@public\n\nThe output of {@link " + commandName + "}.");
// Output types should always be MetadataBearers, possibly in addition
// to a defined output shape.
writer.addImport("MetadataBearer", "__MetadataBearer", TypeScriptDependency.AWS_SDK_TYPES.packageName);
if (outputShape.isPresent()) {
StructureShape output = outputShape.get();
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, output);
if (blobStreamingMembers.isEmpty()) {
List<MemberShape> blobPayloadMembers = getBlobPayloadMembers(model, output);

if (!blobStreamingMembers.isEmpty()) {
writeClientCommandStreamingOutputType(
writer, symbolProvider.toSymbol(output), typeName,
blobStreamingMembers.get(0), commandName
);
} else if (!blobPayloadMembers.isEmpty()) {
writeClientCommandBlobPayloadOutputType(
writer, symbolProvider.toSymbol(output), typeName,
blobPayloadMembers.get(0), commandName
);
} else {
writer.writeDocs("@public\n\nThe output of {@link " + commandName + "}.");
writer.write("export interface $L extends $T, __MetadataBearer {}",
typeName, symbolProvider.toSymbol(outputShape.get()));
} else {
writeClientCommandStreamingOutputType(writer, symbolProvider.toSymbol(output), typeName,
blobStreamingMembers.get(0));
}
} else {
writer.writeDocs("@public\n\nThe output of {@link " + commandName + "}.");
writer.write("export interface $L extends __MetadataBearer {}", typeName);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ public void generateSharedComponents(GenerationContext context) {
generateDocumentBodyShapeSerializers(context, serializingDocumentShapes);
generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes);
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
HttpProtocolGeneratorUtils.generateCollectBody(context);
HttpProtocolGeneratorUtils.generateCollectBodyString(context);
HttpProtocolGeneratorUtils.generateHttpBindingUtils(context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,37 +247,15 @@ static void generateMetadataDeserializer(GenerationContext context, SymbolRefere
writer.write("");
}

/**
* Writes a response body stream collector. This function converts the low-level response body stream to
* Uint8Array binary data.
*
* @param context The generation context.
*/
static void generateCollectBody(GenerationContext context) {
TypeScriptWriter writer = context.getWriter();

writer.addImport("SerdeContext", "__SerdeContext", TypeScriptDependency.SMITHY_TYPES);
writer.write("// Collect low-level response body stream to Uint8Array.");
writer.openBlock("const collectBody = (streamBody: any = new Uint8Array(), context: __SerdeContext): "
+ "Promise<Uint8Array> => {", "};", () -> {
writer.openBlock("if (streamBody instanceof Uint8Array) {", "}", () -> {
writer.write("return Promise.resolve(streamBody);");
});
writer.write("return context.streamCollector(streamBody) || Promise.resolve(new Uint8Array());");
});

writer.write("");
}

/**
* Writes a function converting the low-level response body stream to utf-8 encoded string. It depends on
* response body stream collector {@link #generateCollectBody(GenerationContext)}.
* response body stream collector.
*
* @param context The generation context
*/
static void generateCollectBodyString(GenerationContext context) {
TypeScriptWriter writer = context.getWriter();

writer.addImport("collectBody", null, TypeScriptDependency.AWS_SMITHY_CLIENT);
writer.addImport("SerdeContext", "__SerdeContext", TypeScriptDependency.SMITHY_TYPES);
writer.write("// Encode Uint8Array data into string with utf-8.");
writer.write("const collectBodyString = (streamBody: any, context: __SerdeContext): Promise<string> => "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ public void generateSharedComponents(GenerationContext context) {
generateDocumentBodyShapeSerializers(context, serializingDocumentShapes);
generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes);
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
HttpProtocolGeneratorUtils.generateCollectBody(context);
HttpProtocolGeneratorUtils.generateCollectBodyString(context);

TypeScriptWriter writer = context.getWriter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const compareEquivalentXmlBodies = (
ignoreDeclaration: true,
parseTagValue: false,
trimValues: false,
tagValueProcessor: (_, val) => (val.trim() === "" && val.includes("\n") ? "" : undefined),
tagValueProcessor: (_: any, val: any) => (val.trim() === "" && val.includes("\n") ? "" : undefined),
};

const parseXmlBody = (body: string) => {
Expand Down