Skip to content

Commit

Permalink
feat(clients): apply serde brevity function
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Apr 12, 2023
1 parent 2e13a0f commit 26d676d
Show file tree
Hide file tree
Showing 22 changed files with 2,441 additions and 2,493 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Expand Up @@ -4,3 +4,4 @@ CHANGELOG.md
**/*.hbs
**/*/report.md
clients/*/src/endpoint/ruleset.ts
**/*.java
4 changes: 4 additions & 0 deletions .vscode/settings.json
@@ -1,5 +1,9 @@
{
"editor.tabSize": 2,
"[java]": {
"editor.tabSize": 4
},
"java.format.enabled": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.rulers": [80, 120],
Expand Down
12 changes: 4 additions & 8 deletions Makefile
Expand Up @@ -20,11 +20,7 @@ turbo-build:
npx turbo run build --api="http://localhost:3000" --team="aws-sdk-js" --token="xyz"
node scripts/remote-cache/ stop

# Runs single-client codegen for all clients using Turborepo
turbo-generate-clients:
(cd scripts/remote-cache && yarn)
node scripts/remote-cache/ start&
sleep 3
npx turbo run generate:client --filter=@aws-sdk/client-* --api="http://localhost:3000" --team="aws-sdk-js" --token="xyz"
node scripts/remote-cache/ stop

protocols:
yarn generate-clients -g codegen/sdk-codegen/aws-models/rekognitionstreaming.json
git checkout HEAD clients/client-rekognitionstreaming
yarn test:protocols
Expand Up @@ -43,6 +43,8 @@ final class JsonMemberDeserVisitor extends DocumentMemberDeserVisitor {
Format defaultTimestampFormat) {
super(context, dataSource, defaultTimestampFormat);
this.memberShape = memberShape;
context.getWriter().addImport("_json", null, "@aws-sdk/smithy-client");
serdeElision.setEnabledForModel(true);
}

JsonMemberDeserVisitor(GenerationContext context,
Expand Down
Expand Up @@ -38,6 +38,8 @@ final class JsonMemberSerVisitor extends DocumentMemberSerVisitor {
*/
JsonMemberSerVisitor(GenerationContext context, String dataSource, Format defaultTimestampFormat) {
super(context, dataSource, defaultTimestampFormat);
context.getWriter().addImport("_json", null, "@aws-sdk/smithy-client");
serdeElision.setEnabledForModel(true);
}

@Override
Expand Down
Expand Up @@ -182,4 +182,9 @@ private DocumentMemberDeserVisitor getMemberDeserVisitor(GenerationContext conte
public void generateProtocolTests(GenerationContext context) {
AwsProtocolUtils.generateProtocolTests(this, context);
}

@Override
protected boolean enableSerdeElision() {
return true;
}
}
Expand Up @@ -41,6 +41,7 @@
import software.amazon.smithy.typescript.codegen.integration.DocumentMemberDeserVisitor;
import software.amazon.smithy.typescript.codegen.integration.DocumentShapeDeserVisitor;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
import software.amazon.smithy.typescript.codegen.validation.UnaryFunctionCall;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
Expand All @@ -59,10 +60,10 @@ final class JsonShapeDeserVisitor extends DocumentShapeDeserVisitor {

JsonShapeDeserVisitor(GenerationContext context) {
this(context,
// Use the jsonName trait value if present, otherwise use the member name.
(memberShape, memberName) -> memberShape.getTrait(JsonNameTrait.class)
.map(JsonNameTrait::getValue)
.orElse(memberName));
// Use the jsonName trait value if present, otherwise use the member name.
(memberShape, memberName) -> memberShape.getTrait(JsonNameTrait.class)
.map(JsonNameTrait::getValue)
.orElse(memberName));
}

JsonShapeDeserVisitor(GenerationContext context, BiFunction<MemberShape, String, String> memberNameStrategy) {
Expand All @@ -82,34 +83,52 @@ protected void deserializeCollection(GenerationContext context, CollectionShape

// Filter out null entries if we don't have the sparse trait.
String potentialFilter = "";

if (!shape.hasTrait(SparseTrait.ID) && !artifactType.equals(ArtifactType.SSDK)) {
potentialFilter = ".filter((e: any) => e != null)";
}

writer.openBlock("const retVal = (output || [])$L.map((entry: any) => {", "});", potentialFilter, () -> {
// Short circuit null values from serialization.
writer.openBlock("if (entry === null) {", "}", () -> {
// In the SSDK we want to be very strict about not accepting nulls in non-sparse lists.
if (!shape.hasTrait(SparseTrait.ID) && artifactType.equals(ArtifactType.SSDK)) {
writer.write("throw new TypeError('All elements of the non-sparse list $S must be non-null.');",
shape.getId());
} else {
writer.write("return null as any;");
final String filterExpression = potentialFilter;

String returnExpression = target.accept(getMemberVisitor(shape.getMember(), "entry"));

if (returnExpression.equals("entry")) {
writer.write("const retVal = (output || [])$L", filterExpression);
} else {
writer.openBlock("const retVal = (output || [])$L.map((entry: any) => {", "});",
filterExpression, () -> {
// Short circuit null values from serialization.
if (filterExpression.isEmpty()) {
writer.openBlock("if (entry === null) {", "}", () -> {
// In the SSDK we want to be very strict about not accepting nulls in non-sparse
// lists.
if (!shape.hasTrait(SparseTrait.ID) && artifactType.equals(ArtifactType.SSDK)) {
writer.write(
"throw new TypeError('All elements of the non-sparse list $S must be non-null.');",
shape.getId());
} else {
writer.write("return null as any;");
}
});
}

// Dispatch to the output value provider for any additional handling.
writer.write("return $L$L;",
target.accept(getMemberVisitor(shape.getMember(), "entry")),
usesExpect(target) ? " as any" : "");
}
});
);
}

// Dispatch to the output value provider for any additional handling.
writer.write("return $L$L;", target.accept(getMemberVisitor(shape.getMember(), "entry")),
usesExpect(target) ? " as any" : "");
});
if (shape.isSetShape() && artifactType.equals(ArtifactType.SSDK)) {
writer.addDependency(TypeScriptDependency.SERVER_COMMON);
writer.addImport("findDuplicates", "__findDuplicates", "@aws-smithy/server-common");
writer.openBlock("if (__findDuplicates(retVal).length > 0) {", "}", () -> {
writer.write("throw new TypeError('All elements of the set $S must be unique.');",
shape.getId());
shape.getId());
});
}

writer.write("return retVal;");
}

Expand Down Expand Up @@ -140,6 +159,7 @@ protected void deserializeMap(GenerationContext context, MapShape shape) {
if (shape.hasTrait(SparseTrait.ID)) {
writer.write("acc[key] = null as any;");
}

writer.write("return acc;");
});

Expand All @@ -158,28 +178,74 @@ protected void deserializeStructure(GenerationContext context, StructureShape sh
// Prepare the document contents structure.
// Use a TreeMap to sort the members.
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
writer.openBlock("return {", "} as any;", () -> {
writer.openBlock("return take(output, {", "}) as any;", () -> {
// Set all the members to undefined to meet type constraints.
members.forEach((memberName, memberShape) -> {
String locationName = memberNameStrategy.apply(memberShape, memberName);
String wireName = memberNameStrategy.apply(memberShape, memberName);
boolean hasJsonName = memberShape.hasTrait(JsonNameTrait.class);
Shape target = context.getModel().expectShape(memberShape.getTarget());

String propertyAccess = getFrom("output", locationName);
String propertyAccess = getFrom("output", wireName);
String value = target.accept(getMemberVisitor(memberShape, "_"));

if (usesExpect(target)) {
// Booleans and numbers will call expectBoolean/expectNumber which will handle
// null/undefined properly.
writer.write("$L: $L,",
memberName,
target.accept(getMemberVisitor(memberShape, propertyAccess)));
if (hasJsonName) {
if (usesExpect(target)) {
if (UnaryFunctionCall.check(value)) {
writer.write("$L: [,$L,`$L`],", memberName, UnaryFunctionCall.toRef(value), wireName);
} else {
writer.write("$L: [,$L,`$L`],", memberName, "_ => " + value, wireName);
}
} else {
String valueExpression = target.accept(getMemberVisitor(memberShape, propertyAccess));

if (valueExpression.equals(propertyAccess)) {
writer.write("$L: [,,`$L`],", memberName, wireName);
} else {
String functionExpression = value;
boolean isUnaryCall = UnaryFunctionCall.check(functionExpression);
if (isUnaryCall) {
writer.write("$L: [,$L,`$L`],",
memberName,
UnaryFunctionCall.toRef(functionExpression),
wireName
);
} else {
writer.write("$L: _ => [,$L,`$L`],",
memberName,
functionExpression,
wireName
);
}
}
}
} else {
writer.write(
"$1L: ($2L != null) ? $3L: undefined,",
memberName,
propertyAccess,
// Dispatch to the output value provider for any additional handling.
target.accept(getMemberVisitor(memberShape, propertyAccess))
);
if (usesExpect(target)) {
if (UnaryFunctionCall.check(value)) {
writer.write("$L: $L,", memberName, UnaryFunctionCall.toRef(value));
} else {
writer.write("$L: $L,", memberName, "_ => " + value);
}
} else {
String valueExpression = target.accept(getMemberVisitor(memberShape, propertyAccess));

if (valueExpression.equals(propertyAccess)) {
writer.write("$1L: [],", memberName);
} else {
String functionExpression = value;
boolean isUnaryCall = UnaryFunctionCall.check(functionExpression);
if (isUnaryCall) {
writer.write("$1L: $2L,",
memberName,
UnaryFunctionCall.toRef(functionExpression)
);
} else {
writer.write("$1L: _ => $2L,",
memberName,
functionExpression
);
}
}
}
}
});
});
Expand All @@ -190,8 +256,10 @@ private boolean usesExpect(Shape shape) {
if (shape.hasTrait(MediaTypeTrait.class)) {
return !CodegenUtils.isJsonMediaType(shape.expectTrait(MediaTypeTrait.class).getValue());
}

return true;
}

return shape.isBooleanShape() || shape instanceof NumberShape;
}

Expand All @@ -207,6 +275,7 @@ protected void deserializeUnion(GenerationContext context, UnionShape shape) {
String locationName = memberNameStrategy.apply(memberShape, memberName);

String memberValue = target.accept(getMemberVisitor(memberShape, getFrom("output", locationName)));

if (usesExpect(target)) {
// Booleans and numbers will call expectBoolean/expectNumber which will handle
// null/undefined properly.
Expand All @@ -230,4 +299,4 @@ protected void deserializeUnion(GenerationContext context, UnionShape shape) {
// Or write to the unknown member the element in the output.
writer.write("return { $$unknown: Object.entries(output)[0] };");
}
}
}
Expand Up @@ -84,15 +84,21 @@ public void serializeCollection(GenerationContext context, CollectionShape shape
potentialFilter = ".filter((e: any) => e != null)";
}

writer.openBlock("return input$L.map(entry => {", "});", potentialFilter, () -> {
// Short circuit null values from serialization.
if (hasSparseTrait) {
writer.write("if (entry === null) { return null as any; }");
}

// Dispatch to the input value provider for any additional handling.
writer.write("return $L;", target.accept(getMemberVisitor("entry")));
});
String returnedExpression = target.accept(getMemberVisitor("entry"));

if (returnedExpression.equals("entry")) {
writer.write("return input$L;", potentialFilter);
} else {
writer.openBlock("return input$L.map(entry => {", "});", potentialFilter, () -> {
// Short circuit null values from serialization.
if (hasSparseTrait) {
writer.write("if (entry === null) { return null as any; }");
}

// Dispatch to the input value provider for any additional handling.
writer.write("return $L;", target.accept(getMemberVisitor("entry")));
});
}
}

@Override
Expand Down Expand Up @@ -133,26 +139,43 @@ public void serializeMap(GenerationContext context, MapShape shape) {
@Override
public void serializeStructure(GenerationContext context, StructureShape shape) {
TypeScriptWriter writer = context.getWriter();
writer.addImport("take", null, "@aws-sdk/smithy-client");

writer.openBlock("return {", "};", () -> {
writer.openBlock("return take(input, {", "});", () -> {
// Use a TreeMap to sort the members.
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
members.forEach((memberName, memberShape) -> {
String locationName = memberNameStrategy.apply(memberShape, memberName);
String wireName = memberNameStrategy.apply(memberShape, memberName);
boolean hasJsonName = memberShape.hasTrait(JsonNameTrait.class);
Shape target = context.getModel().expectShape(memberShape.getTarget());
String inputLocation = "input." + memberName;

// Handle @timestampFormat on members not just the targeted shape.
String valueProvider = memberShape.hasTrait(TimestampFormatTrait.class)
String valueProvider = "_ => " + (memberShape.hasTrait(TimestampFormatTrait.class)
? AwsProtocolUtils.getInputTimestampValueProvider(context, memberShape,
TIMESTAMP_FORMAT, inputLocation)
: target.accept(getMemberVisitor(inputLocation));

if (memberShape.hasTrait(IdempotencyTokenTrait.class)) {
writer.write("'$L': $L ?? generateIdempotencyToken(),", locationName, valueProvider);
TIMESTAMP_FORMAT, "_")
: target.accept(getMemberVisitor("_")));

if (hasJsonName) {
if (memberShape.hasTrait(IdempotencyTokenTrait.class)) {
writer.write("'$L': [, _ => _ ?? generateIdempotencyToken(), `$L`],", wireName, memberName);
} else {
if (valueProvider.equals("_ => _")) {
writer.write("$L: [,,`$L`],", wireName, memberName);
} else {
writer.write("$L: [,$L,`$L`],", wireName, valueProvider, memberName);
}
}
} else {
writer.write("...($1L != null && { $2S: $3L }),",
inputLocation, locationName, valueProvider);
if (memberShape.hasTrait(IdempotencyTokenTrait.class)) {
writer.write("'$L': _ => _ ?? generateIdempotencyToken(),", memberName);
} else {
if (valueProvider.equals("_ => _")) {
writer.write("$1S: [],", memberName);
} else {
writer.write("$1S: $2L,", memberName, valueProvider);
}
}
}
});

Expand Down

0 comments on commit 26d676d

Please sign in to comment.