Skip to content
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 @@ -134,4 +134,20 @@ static void writeStreamingMemberType(
+ " `%2$s` defined in {@link %1$s}", containerSymbol.getName(), memberName));
writer.write("export interface $1L extends $1LType {}", typeName);
}

/**
* Ease the input streaming member restriction so that users don't need to construct a stream every time.
* This is used for inline type declarations (such as parameters) that need to take more permissive inputs
* Refer here for more rationales: https://github.com/aws/aws-sdk-js-v3/issues/843
*/
static void writeInlineStreamingMemberType(
TypeScriptWriter writer,
Symbol containerSymbol,
MemberShape streamingMember
) {
String memberName = streamingMember.getMemberName();
String optionalSuffix = streamingMember.isRequired() ? "" : "?";
writer.writeInline("Omit<$1T, $2S> & { $2L$3L: $1T[$2S]|string|Uint8Array|Buffer }",
containerSymbol, memberName, optionalSuffix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,20 @@ private void generateServerRequestTest(OperationShape operation, HttpRequestTest
// We use a partial here so that we don't have to define the entire service, but still get the advantages
// the type checker, including excess property checking. Later on we'll use `as` to cast this to the
// full service so that we can actually use it.
writer.openBlock("const testService: Partial<$T> = {", "};", serviceSymbol, () -> {
writer.write("$L: testFunction as $T,", operationSymbol.getName(), operationSymbol);
writer.openBlock("const testService: Partial<$T<{}>> = {", "};", serviceSymbol, () -> {
writer.write("$L: testFunction as $T<{}>,", operationSymbol.getName(), operationSymbol);
});

String getHandlerName = "get" + handlerSymbol.getName();
writer.addImport(getHandlerName, null, "./server/");
writer.addImport("ValidationFailure", "__ValidationFailure", "@aws-smithy/server-common");

// Cast the service as any so TS will ignore the fact that the type being passed in is incomplete.
writer.write("const handler = $L(testService as $T);", getHandlerName, serviceSymbol);
writer.openBlock(
"const handler = $L(testService as $T<{}>, (ctx: {}, failures: __ValidationFailure[]) => {",
"});", getHandlerName, serviceSymbol,
() -> writer.write("if (failures) { throw failures; } return undefined;")
);

// Construct a new http request according to the test case definition.
writer.openBlock("const request = new HttpRequest({", "});", () -> {
Expand Down Expand Up @@ -609,7 +614,9 @@ private void writeServerResponseTest(OperationShape operation, HttpResponseTestC

writer.addImport("serializeFrameworkException", null,
"./protocols/" + ProtocolGenerator.getSanitizedName(protocolGenerator.getName()));
writer.write("const handler = new $T(service, testMux, serFn, serializeFrameworkException);", handlerSymbol);
writer.addImport("ValidationFailure", "__ValidationFailure", "@aws-smithy/server-common");
writer.write("const handler = new $T(service, testMux, serFn, serializeFrameworkException, "
+ "(ctx: {}, f: __ValidationFailure[]) => { if (f) { throw f; } return undefined;});", handlerSymbol);
writer.write("let r = await handler.handle(request, {})").write("");
writeHttpResponseAssertions(testCase);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ private void writeInputType(String typeName, Optional<StructureShape> inputShape
} else {
// If the input is non-existent, then use an empty object.
writer.write("export interface $L {}", typeName);
writer.openBlock("export namespace $L {", "}", typeName, () -> {
writer.addImport("ValidationFailure", "__ValidationFailure", "@aws-smithy/server-common");
writer.writeDocs("@internal");
writer.write("export const validate: () => __ValidationFailure[] = () => [];");
});
}
}

Expand All @@ -110,8 +115,10 @@ private void renderNamespace(String typeName, StructureShape input) {
writer.openBlock("export namespace $L {", "}", typeName, () -> {
writer.addImport("ValidationFailure", "__ValidationFailure", "@aws-smithy/server-common");
writer.writeDocs("@internal");
writer.write("export const validate: (obj: $L) => __ValidationFailure[] = $T.validate;",
symbol.getName(), symbol);
// Streaming makes the type of the object being validated weird on occasion.
// Using `Parameters` here means we don't have to try to derive the weird type twice
writer.write("export const validate: (obj: Parameters<typeof $1T.validate>[0]) => "
+ "__ValidationFailure[] = $1T.validate;", symbol);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@

package software.amazon.smithy.typescript.codegen;

import static software.amazon.smithy.typescript.codegen.CodegenUtils.getBlobStreamingMembers;
import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeInlineStreamingMemberType;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.codegen.core.SymbolReference;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.typescript.codegen.integration.HttpProtocolGeneratorUtils;
Expand Down Expand Up @@ -215,17 +220,17 @@ private void renderStructureNamespace(StructuredMemberWriter structuredMemberWri

writer.addImport("ValidationFailure", "__ValidationFailure", "@aws-smithy/server-common");
writer.writeDocs("@internal");
writer.openBlock("export const validate = ($L: $L, path: string = \"\"): __ValidationFailure[] => {", "}",
objectParam, symbol.getName(),
() -> {
// TODO: move this somewhere so it only gets run once.
// Putting it at the top of the namespace can result in runtime errors when
// you have mutually recursive structures because the validator of one will
// be defined before the validator of the other exists at all.
structuredMemberWriter.writeMemberValidatorFactory(writer, "memberValidators");
structuredMemberWriter.writeValidateMethodContents(writer, objectParam);
}
);
List<MemberShape> blobStreamingMembers = getBlobStreamingMembers(model, shape);
writer.writeInline("export const validate = ($L: ", objectParam);
if (blobStreamingMembers.isEmpty()) {
writer.writeInline("$L", symbol.getName());
} else {
writeInlineStreamingMemberType(writer, symbol, blobStreamingMembers.get(0));
}
writer.openBlock(", path: string = \"\"): __ValidationFailure[] => {", "}", () -> {
structuredMemberWriter.writeMemberValidatorFactory(writer, "memberValidators");
structuredMemberWriter.writeValidateMethodContents(writer, objectParam);
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import software.amazon.smithy.model.traits.EnumTrait;
import software.amazon.smithy.model.traits.IdempotencyTokenTrait;
import software.amazon.smithy.model.traits.LengthTrait;
import software.amazon.smithy.model.traits.MediaTypeTrait;
import software.amazon.smithy.model.traits.PatternTrait;
import software.amazon.smithy.model.traits.RangeTrait;
import software.amazon.smithy.model.traits.RequiredTrait;
Expand Down Expand Up @@ -347,8 +348,14 @@ void writeMemberValidatorFactory(TypeScriptWriter writer, String cacheName) {
void writeValidateMethodContents(TypeScriptWriter writer, String param) {
writer.openBlock("return [", "];", () -> {
for (MemberShape member : members) {
writer.write("...getMemberValidator($1S).validate($2L.$1L, `$${path}/$3L`),",
getSanitizedMemberName(member), param, member.getMemberName());
String optionalSuffix = "";
if (member.getMemberTrait(model, MediaTypeTrait.class).isPresent()
&& model.expectShape(member.getTarget()) instanceof StringShape) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a .isStringShape() method but eh

// lazy JSON wrapper validation should be done based on the serialized form of the object
optionalSuffix = "?.toString()";
}
writer.write("...getMemberValidator($1S).validate($2L.$1L$4L, `$${path}/$3L`),",
getSanitizedMemberName(member), param, member.getMemberName(), optionalSuffix);
}
});
}
Expand Down Expand Up @@ -553,6 +560,14 @@ private Symbol getSymbolForValidatedType(Shape shape) {
return symbolProvider.toSymbol(model.expectShape(ShapeId.from("smithy.api#String")));
}

// Streaming blob inputs can also take string, Uint8Array and Buffer, so we widen the symbol
if (shape.isBlobShape() && shape.hasTrait(StreamingTrait.class)) {
return symbolProvider.toSymbol(shape)
.toBuilder()
.name("Readable | ReadableStream | Blob | string | Uint8Array | Buffer")
.build();
}

return symbolProvider.toSymbol(shape);
}

Expand Down