Skip to content

Commit bd29e4e

Browse files
feat(lib-dynamodb): generic typesnamed helpers for nested document shapes
1 parent b3c0959 commit bd29e4e

12 files changed

Lines changed: 789 additions & 1268 deletions

File tree

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/DocumentClientCommandGenerator.java

Lines changed: 82 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import java.nio.file.Paths;
99
import java.util.ArrayList;
1010
import java.util.HashSet;
11+
import java.util.LinkedHashMap;
1112
import java.util.List;
1213
import java.util.Map;
1314
import java.util.Optional;
15+
import java.util.Set;
1416
import java.util.TreeMap;
1517
import java.util.stream.Collectors;
1618
import software.amazon.smithy.codegen.core.CodegenException;
@@ -33,7 +35,6 @@
3335

3436
@SmithyInternalApi
3537
final class DocumentClientCommandGenerator implements Runnable {
36-
3738
static final String COMMAND_PROPERTIES_SECTION = "command_properties";
3839
static final String COMMAND_BODY_EXTRA_SECTION = "command_body_extra";
3940
static final String COMMAND_CONSTRUCTOR_SECTION = "command_constructor";
@@ -55,6 +56,10 @@ final class DocumentClientCommandGenerator implements Runnable {
5556
private final List<MemberShape> outputMembersWithAttr;
5657
private final String clientCommandClassName;
5758
private final String clientCommandLocalName;
59+
private final String helperTypePrefix;
60+
private final Map<String, String> structureHelperTypeNames = new LinkedHashMap<>();
61+
private final List<StructureShape> structureHelperTypes = new ArrayList<>();
62+
private final Set<String> reservedHelperTypeNames = new HashSet<>();
5863
/**
5964
* Map of package name to external:local name entries.
6065
*/
@@ -90,6 +95,9 @@ final class DocumentClientCommandGenerator implements Runnable {
9095

9196
clientCommandClassName = symbol.getName();
9297
clientCommandLocalName = "__" + clientCommandClassName;
98+
helperTypePrefix = DocumentClientUtils.getModifiedName(symbol.getName().replaceAll("Command$", ""));
99+
reservedHelperTypeNames.add(inputTypeName);
100+
reservedHelperTypeNames.add(outputTypeName);
93101
}
94102

95103
@Override
@@ -320,151 +328,50 @@ private void writeStructureKeyNode(StructureShape structureTarget) {
320328
});
321329
}
322330

323-
/**
324-
* Detects operations requiring custom TransactWrite item type generation by inspecting
325-
* the input shape structure (TransactItems list of structures with ConditionCheck/Put/Delete/Update).
326-
*/
327-
private boolean requiresTransactWriteItemTypes() {
328-
Optional<StructureShape> inputOpt = operationIndex.getInput(operation);
329-
if (inputOpt.isEmpty()) {
330-
return false;
331-
}
332-
StructureShape inputShape = inputOpt.get();
333-
if (!inputShape.getMember("TransactItems").isPresent()) {
334-
return false;
335-
}
336-
Shape transactItemsTarget = model.expectShape(
337-
inputShape.getMember("TransactItems").get().getTarget()
338-
);
339-
if (!transactItemsTarget.isListShape()) {
340-
return false;
341-
}
342-
Shape itemShape = model.expectShape(
343-
((CollectionShape) transactItemsTarget).getMember().getTarget()
344-
);
345-
if (!itemShape.isStructureShape()) {
346-
return false;
331+
private void generateInputAndOutputTypes() {
332+
collectHelperTypes(inputMembersWithAttr);
333+
collectHelperTypes(outputMembersWithAttr);
334+
335+
for (StructureShape structureShape : structureHelperTypes) {
336+
writer.write("");
337+
writeNamedStructureOmitType(structureShape);
347338
}
348-
StructureShape itemStructure = (StructureShape) itemShape;
349-
return itemStructure.getMember("ConditionCheck").isPresent()
350-
&& itemStructure.getMember("Put").isPresent()
351-
&& itemStructure.getMember("Delete").isPresent()
352-
&& itemStructure.getMember("Update").isPresent();
353-
}
354339

355-
private void generateInputAndOutputTypes() {
356340
writer.write("");
357-
if (requiresTransactWriteItemTypes()) {
358-
generateTransactWriteItemTypes();
359-
writeTransactWriteInputType();
360-
} else {
361-
writeType(inputTypeName, originalInputTypeName, operationIndex.getInput(operation), inputMembersWithAttr);
362-
}
341+
writeType(inputTypeName, originalInputTypeName, operationIndex.getInput(operation), inputMembersWithAttr);
363342
writer.write("");
364343
writeType(outputTypeName, originalOutputTypeName, operationIndex.getOutput(operation), outputMembersWithAttr);
365344
writer.write("");
366345
}
367346

368-
private void generateTransactWriteItemTypes() {
369-
String clientPkg = AwsDependency.CLIENT_DYNAMODB_PEER.getPackageName();
370-
String utilPkg = AwsDependency.UTIL_DYNAMODB.getPackageName();
371-
372-
registerTypeImport("ConditionCheck", "ConditionCheck", clientPkg);
373-
registerTypeImport("Put", "Put", clientPkg);
374-
registerTypeImport("Delete", "Delete", clientPkg);
375-
registerTypeImport("Update", "Update", clientPkg);
376-
registerTypeImport("TransactWriteItem", "ClientTransactWriteItem", clientPkg);
377-
registerTypeImport("NativeAttributeValue", "NativeAttributeValue", utilPkg);
347+
private void collectHelperTypes(List<MemberShape> membersWithAttr) {
348+
for (MemberShape member : membersWithAttr) {
349+
collectHelperTypes(member, new HashSet<>());
350+
}
351+
}
378352

379-
writer.writeDocs(
380-
"Document client variant of ConditionCheck for TransactWrite.\n"
381-
+ "Uses native JavaScript types (NativeAttributeValue) instead of AttributeValue.\n@public"
382-
);
383-
writer.openBlock(
384-
"export type TransactWriteConditionCheck = Omit<ConditionCheck, \"Key\" | \"ExpressionAttributeValues\"> & {",
385-
"};",
386-
() -> {
387-
writer.write("Key: Record<string, NativeAttributeValue> | undefined;");
388-
writer.write("ExpressionAttributeValues?: Record<string, NativeAttributeValue> | undefined;");
353+
private void collectHelperTypes(MemberShape member, Set<String> parents) {
354+
Shape memberTarget = model.expectShape(member.getTarget());
355+
if (memberTarget.isStructureShape()) {
356+
StructureShape structureTarget = (StructureShape) memberTarget;
357+
String structureId = structureTarget.getId().toString();
358+
if (!parents.add(structureId)) {
359+
return;
389360
}
390-
);
391-
writer.write("");
392361

393-
writer.writeDocs(
394-
"Document client variant of Put for TransactWrite.\n"
395-
+ "Uses native JavaScript types (NativeAttributeValue) instead of AttributeValue.\n@public"
396-
);
397-
writer.openBlock(
398-
"export type TransactWritePut = Omit<Put, \"Item\" | \"ExpressionAttributeValues\"> & {",
399-
"};",
400-
() -> {
401-
writer.write("Item: Record<string, NativeAttributeValue> | undefined;");
402-
writer.write("ExpressionAttributeValues?: Record<string, NativeAttributeValue> | undefined;");
362+
List<MemberShape> membersWithAttr = getStructureMembersWithAttr(Optional.of(structureTarget));
363+
for (MemberShape memberWithAttr : membersWithAttr) {
364+
collectHelperTypes(memberWithAttr, parents);
403365
}
404-
);
405-
writer.write("");
406-
407-
writer.writeDocs(
408-
"Document client variant of Delete for TransactWrite.\n"
409-
+ "Uses native JavaScript types (NativeAttributeValue) instead of AttributeValue.\n@public"
410-
);
411-
writer.openBlock(
412-
"export type TransactWriteDelete = Omit<Delete, \"Key\" | \"ExpressionAttributeValues\"> & {",
413-
"};",
414-
() -> {
415-
writer.write("Key: Record<string, NativeAttributeValue> | undefined;");
416-
writer.write("ExpressionAttributeValues?: Record<string, NativeAttributeValue> | undefined;");
366+
if (!membersWithAttr.isEmpty()) {
367+
getStructureHelperTypeName(structureTarget);
417368
}
418-
);
419-
writer.write("");
420-
421-
writer.writeDocs(
422-
"Document client variant of Update for TransactWrite.\n"
423-
+ "Uses native JavaScript types (NativeAttributeValue) instead of AttributeValue.\n@public"
424-
);
425-
writer.openBlock(
426-
"export type TransactWriteUpdate = Omit<Update, \"Key\" | \"ExpressionAttributeValues\"> & {",
427-
"};",
428-
() -> {
429-
writer.write("Key: Record<string, NativeAttributeValue> | undefined;");
430-
writer.write("ExpressionAttributeValues?: Record<string, NativeAttributeValue> | undefined;");
431-
}
432-
);
433-
writer.write("");
434-
435-
writer.writeDocs(
436-
"Document client variant of TransactWriteItem.\n"
437-
+ "Uses native JavaScript types (NativeAttributeValue) instead of AttributeValue.\n"
438-
+ "Each item must have exactly one of ConditionCheck, Put, Delete, or Update.\n@public"
439-
);
440-
writer.openBlock(
441-
"export type TransactWriteItem = Omit<ClientTransactWriteItem, "
442-
+ "\"ConditionCheck\" | \"Put\" | \"Delete\" | \"Update\"> & {",
443-
"};",
444-
() -> {
445-
writer.write("ConditionCheck?: TransactWriteConditionCheck;");
446-
writer.write("Put?: TransactWritePut;");
447-
writer.write("Delete?: TransactWriteDelete;");
448-
writer.write("Update?: TransactWriteUpdate;");
449-
}
450-
);
451-
writer.write("");
452-
}
453-
454-
private void writeTransactWriteInputType() {
455-
registerTypeImport(
456-
originalInputTypeName,
457-
"__" + originalInputTypeName,
458-
AwsDependency.CLIENT_DYNAMODB_PEER.getPackageName()
459-
);
460-
writer.writeDocs("@public");
461-
writer.openBlock(
462-
"export type $L = Omit<__$L, \"TransactItems\"> & {",
463-
"};",
464-
inputTypeName,
465-
originalInputTypeName,
466-
() -> writer.write("TransactItems: TransactWriteItem[] | undefined;")
467-
);
369+
parents.remove(structureId);
370+
} else if (memberTarget.isMapShape()) {
371+
collectHelperTypes(((MapShape) memberTarget).getValue(), parents);
372+
} else if (memberTarget instanceof CollectionShape) {
373+
collectHelperTypes(((CollectionShape) memberTarget).getMember(), parents);
374+
}
468375
}
469376

470377
private List<MemberShape> getStructureMembersWithAttr(Optional<StructureShape> optionalShape) {
@@ -518,20 +425,18 @@ private void writeType(
518425
}
519426
}
520427

521-
private void writeStructureOmitType(StructureShape structureTarget) {
428+
private void writeNamedStructureOmitType(StructureShape structureTarget) {
522429
List<MemberShape> membersWithAttr = getStructureMembersWithAttr(Optional.of(structureTarget));
523430
String memberUnionToOmit = membersWithAttr.stream()
524431
.map(memberWithAttr -> "'" + symbolProvider.toMemberName(memberWithAttr) + "'")
525432
.collect(Collectors.joining(" | "));
526-
String typeNameToOmit = symbolProvider.toSymbol(structureTarget).getName();
527-
registerTypeImport(
528-
typeNameToOmit,
529-
typeNameToOmit,
530-
AwsDependency.CLIENT_DYNAMODB_PEER.getPackageName()
531-
);
433+
String typeNameToOmit = getStructureBaseTypeName(structureTarget);
434+
435+
writer.writeDocs("@public");
532436
writer.openBlock(
533-
"Omit<$L, $L> & {",
534-
"}",
437+
"export type $L = Omit<$L, $L> & {",
438+
"};",
439+
getStructureHelperTypeName(structureTarget),
535440
typeNameToOmit,
536441
memberUnionToOmit,
537442
() -> {
@@ -558,7 +463,7 @@ private void writeStructureMemberOmitType(MemberShape member) {
558463
private void writeMemberOmitType(MemberShape member, boolean allowUndefined) {
559464
Shape memberTarget = model.expectShape(member.getTarget());
560465
if (memberTarget.isStructureShape()) {
561-
writeStructureOmitType((StructureShape) memberTarget);
466+
writer.write(getStructureHelperTypeName((StructureShape) memberTarget));
562467
} else if (memberTarget.isUnionShape()) {
563468
if (symbolProvider.toSymbol(memberTarget).getName().equals("AttributeValue")) {
564469
writeNativeAttributeValue();
@@ -588,6 +493,40 @@ private void writeMemberOmitType(MemberShape member, boolean allowUndefined) {
588493
}
589494
}
590495

496+
private String getStructureHelperTypeName(StructureShape structureTarget) {
497+
String structureId = structureTarget.getId().toString();
498+
if (structureHelperTypeNames.containsKey(structureId)) {
499+
return structureHelperTypeNames.get(structureId);
500+
}
501+
502+
String shapeName = symbolProvider.toSymbol(structureTarget).getName();
503+
String preferredName = shapeName.startsWith(helperTypePrefix)
504+
? shapeName
505+
: helperTypePrefix + shapeName;
506+
String helperTypeName = preferredName;
507+
int collisionSuffix = 2;
508+
while (!reservedHelperTypeNames.add(helperTypeName)) {
509+
helperTypeName = preferredName + collisionSuffix++;
510+
}
511+
512+
structureHelperTypeNames.put(structureId, helperTypeName);
513+
structureHelperTypes.add(structureTarget);
514+
return helperTypeName;
515+
}
516+
517+
private String getStructureBaseTypeName(StructureShape structureTarget) {
518+
String externalName = symbolProvider.toSymbol(structureTarget).getName();
519+
String helperTypeName = getStructureHelperTypeName(structureTarget);
520+
String localName = externalName.equals(helperTypeName) ? "Client" + externalName : externalName;
521+
522+
registerTypeImport(
523+
externalName,
524+
localName,
525+
AwsDependency.CLIENT_DYNAMODB_PEER.getPackageName()
526+
);
527+
return localName;
528+
}
529+
591530
private void writeNativeAttributeValue() {
592531
String nativeAttributeValue = "NativeAttributeValue";
593532
registerTypeImport(

0 commit comments

Comments
 (0)