From d9297957de5dd0dcb7f5ec2fdf3c39a0771ac1d9 Mon Sep 17 00:00:00 2001 From: George Fu Date: Tue, 12 Mar 2024 14:32:02 -0400 Subject: [PATCH] docs: add more information about BLOB values in structures (#1182) * docs: add more information about BLOB values in structures * escape char * update tests, formatting --- .../typescript/codegen/CommandGenerator.java | 4 +- .../StructureExampleGenerator.java | 127 ++++--- .../StructureExampleGeneratorTest.java | 356 ++++++++++-------- 3 files changed, 283 insertions(+), 204 deletions(-) diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java index 50858c4cafa..fd89c3d7cd4 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java @@ -212,12 +212,12 @@ private String getCommandExample(String serviceName, String configName, String c + String.format("const client = new %s(config);%n", serviceName) + String.format("const input = %s%n", StructureExampleGenerator.generateStructuralHintDocumentation( - model.getShape(operation.getInputShape()).get(), model, false)) + model.getShape(operation.getInputShape()).get(), model, false, true)) + String.format("const command = new %s(input);%n", commandName) + "const response = await client.send(command);\n" + String.format("%s%n", StructureExampleGenerator.generateStructuralHintDocumentation( - model.getShape(operation.getOutputShape()).get(), model, true)) + model.getShape(operation.getOutputShape()).get(), model, true, false)) + "\n```\n" + "\n" + String.format("@param %s - {@link %s}%n", commandInput, commandInput) diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGenerator.java index cd5a99f213c..11e23bef5d4 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGenerator.java @@ -32,7 +32,7 @@ public abstract class StructureExampleGenerator { /** * Generates an example structure for API documentation, as an * automated gap filler for operations that do not have - * hand written examples. + * handwritten examples. * * Example for Athena::createPreparedStatement * ```js @@ -44,65 +44,74 @@ public abstract class StructureExampleGenerator { * }; * ``` */ - public static String generateStructuralHintDocumentation(Shape shape, Model model, boolean isComment) { + public static String generateStructuralHintDocumentation( + Shape shape, + Model model, + boolean isComment, + boolean isInput + ) { StringBuilder buffer = new StringBuilder(); - shape(shape, buffer, model, 0, new ShapeTracker()); + shape(shape, buffer, model, 0, new ShapeTracker(), isInput); // replace non-leading whitespace with single space. String s = Arrays.stream( buffer.toString() - .split("\n")) - .map(line -> line.replaceAll( - "([\\w\\\",:\\[\\{] )\\s+", - "$1") - .replaceAll("\\s+$", "")) - .collect(Collectors.joining((isComment) ? "\n// " : "\n")); + .split("\n")) + .map(line -> line.replaceAll( + "([\\w\\\",:\\[\\{] )\\s+", + "$1") + .replaceAll("\\s+$", "")) + .collect(Collectors.joining((isComment) ? "\n// " : "\n")); return ((isComment) ? "// " : "") + s.replaceAll(",$", ";"); } private static void structure(StructureShape structureShape, - StringBuilder buffer, Model model, - int indentation, - ShapeTracker shapeTracker) { - if (structureShape.getAllMembers().size() == 0) { + StringBuilder buffer, + Model model, + int indentation, + ShapeTracker shapeTracker, + boolean isInput) { + if (structureShape.getAllMembers().isEmpty()) { append(indentation, buffer, "{},"); checkRequired(indentation, buffer, structureShape); } else { append(indentation, buffer, - "{" + (shapeTracker.getOccurrenceCount(structureShape) == 1 - ? " // " + structureShape.getId().getName() - : "")); + "{" + (shapeTracker.getOccurrenceCount(structureShape) == 1 + ? " // " + structureShape.getId().getName() + : "")); checkRequired(indentation, buffer, structureShape); structureShape.getAllMembers().values().forEach(member -> { append(indentation + 2, buffer, member.getMemberName() + ": "); - shape(member, buffer, model, indentation + 2, shapeTracker); + shape(member, buffer, model, indentation + 2, shapeTracker, isInput); }); append(indentation, buffer, "},\n"); } } private static void union(UnionShape unionShape, - StringBuilder buffer, - Model model, - int indentation, - ShapeTracker shapeTracker) { + StringBuilder buffer, + Model model, + int indentation, + ShapeTracker shapeTracker, + boolean isInput) { append(indentation, buffer, "{" + (shapeTracker.getOccurrenceCount(unionShape) == 1 - ? " // " + unionShape.getId().getName() - : "// ") + " Union: only one key present"); + ? " // " + unionShape.getId().getName() + : "// ") + " Union: only one key present"); checkRequired(indentation, buffer, unionShape); unionShape.getAllMembers().values().forEach(member -> { append(indentation + 2, buffer, member.getMemberName() + ": "); - shape(member, buffer, model, indentation + 2, shapeTracker); + shape(member, buffer, model, indentation + 2, shapeTracker, isInput); }); append(indentation, buffer, "},\n"); } private static void shape(Shape shape, - StringBuilder buffer, - Model model, - int indentation, - ShapeTracker shapeTracker) { + StringBuilder buffer, + Model model, + int indentation, + ShapeTracker shapeTracker, + boolean isInput) { Shape target; if (shape instanceof MemberShape) { target = model.getShape(((MemberShape) shape).getTarget()).get(); @@ -123,17 +132,31 @@ private static void shape(Shape shape, append(indentation, buffer, "Number(\"bigint\"),"); break; case BLOB: - if (target.hasTrait(StreamingTrait.class)) { - append(indentation, buffer, "\"STREAMING_BLOB_VALUE\","); + if (isInput) { + if (target.hasTrait(StreamingTrait.class)) { + append(indentation, buffer, + "\"MULTIPLE_TYPES_ACCEPTED\", // see \\@smithy/types -> StreamingBlobPayloadInputTypes" + ); + } else { + append(indentation, buffer, + """ + new Uint8Array(), // e.g. Buffer.from("") or new TextEncoder().encode("")"""); + } } else { - append(indentation, buffer, "\"BLOB_VALUE\","); + if (target.hasTrait(StreamingTrait.class)) { + append(indentation, buffer, + "\"\", // see \\@smithy/types -> StreamingBlobPayloadOutputTypes"); + } else { + append(indentation, buffer, + "new Uint8Array(),"); + } } break; case BOOLEAN: append(indentation, buffer, "true || false,"); break; case BYTE: - append(indentation, buffer, "\"BYTE_VALUE\","); + append(indentation, buffer, "0, // BYTE_VALUE"); break; case DOCUMENT: append(indentation, buffer, "\"DOCUMENT_VALUE\","); @@ -163,50 +186,50 @@ private static void shape(Shape shape, case SET: case LIST: append(indentation, buffer, "[" + (shapeTracker.getOccurrenceCount(target) == 1 - ? " // " + target.getId().getName() - : "")); + ? " // " + target.getId().getName() + : "")); checkRequired(indentation, buffer, shape); ListShape list = (ListShape) target; - shape(list.getMember(), buffer, model, indentation + 2, shapeTracker); + shape(list.getMember(), buffer, model, indentation + 2, shapeTracker, isInput); append(indentation, buffer, "],\n"); break; case MAP: append(indentation, buffer, "{" + (shapeTracker.getOccurrenceCount(target) == 1 - ? " // " + target.getId().getName() - : "")); + ? " // " + target.getId().getName() + : "")); checkRequired(indentation, buffer, shape); append(indentation + 2, buffer, "\"\": "); MapShape map = (MapShape) target; shape(model.getShape(map.getValue().getTarget()).get(), buffer, model, indentation + 2, - shapeTracker); + shapeTracker, isInput); append(indentation, buffer, "},\n"); break; case STRUCTURE: StructureShape structure = (StructureShape) target; - structure(structure, buffer, model, indentation, shapeTracker); + structure(structure, buffer, model, indentation, shapeTracker, isInput); break; case UNION: UnionShape union = (UnionShape) target; - union(union, buffer, model, indentation, shapeTracker); + union(union, buffer, model, indentation, shapeTracker, isInput); break; case ENUM: EnumShape enumShape = (EnumShape) target; String enumeration = enumShape.getEnumValues() - .values() - .stream() - .map(s -> "\"" + s + "\"") - .collect(Collectors.joining(" || ")); + .values() + .stream() + .map(s -> "\"" + s + "\"") + .collect(Collectors.joining(" || ")); append(indentation, buffer, enumeration + ","); break; case INT_ENUM: IntEnumShape intEnumShape = (IntEnumShape) target; String intEnumeration = intEnumShape.getEnumValues() - .values() - .stream() - .map(i -> Integer.toString(i)) - .collect(Collectors.joining(" || ")); + .values() + .stream() + .map(i -> Integer.toString(i)) + .collect(Collectors.joining(" || ")); append(indentation, buffer, intEnumeration + ","); break; case OPERATION: @@ -272,8 +295,8 @@ private static void append(int indentation, StringBuilder buffer, String tail) { * This handles the case of recursive shapes. */ private static class ShapeTracker { - private Map> depths = new HashMap>(); - private Map occurrences = new HashMap(); + private final Map> depths = new HashMap<>(); + private final Map occurrences = new HashMap<>(); /** * Mark that a shape is observed at depth. @@ -291,8 +314,8 @@ public void mark(Shape shape, int depth) { */ public boolean shouldTruncate(Shape shape) { return (shape instanceof MapShape || shape instanceof UnionShape || shape instanceof StructureShape - || shape instanceof ListShape || shape instanceof SetShape) - && (getOccurrenceCount(shape) > 5 || getOccurrenceDepths(shape) > 2); + || shape instanceof ListShape || shape instanceof SetShape) + && (getOccurrenceCount(shape) > 5 || getOccurrenceDepths(shape) > 2); } /** diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGeneratorTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGeneratorTest.java index 938a857634c..83ce12608a8 100644 --- a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGeneratorTest.java +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/documentation/StructureExampleGeneratorTest.java @@ -4,198 +4,254 @@ import static org.hamcrest.Matchers.equalTo; import java.util.List; + import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.BlobShape; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MapShape; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.StreamingTrait; public class StructureExampleGeneratorTest { StringShape string = StringShape.builder() - .id("foo.bar#string") - .build(); + .id("foo.bar#string") + .build(); + + BlobShape blob = BlobShape.builder() + .id("foo.bar#blob") + .build(); + + BlobShape streamingBlob = BlobShape.builder() + .id("foo.bar#streamingBlob") + .traits( + List.of( + new StreamingTrait() + ) + ) + .build(); ListShape list = ListShape.builder() - .id("foo.bar#list") - .member(string.getId()) - .build(); + .id("foo.bar#list") + .member(string.getId()) + .build(); MapShape map = MapShape.builder() - .id("foo.bar#map") - .key(MemberShape.builder() - .id("foo.bar#map$member") - .target(string.getId()) - .build()) - .value(MemberShape.builder() - .id("foo.bar#map$member") - .target(string.getId()) - .build()) - .build(); + .id("foo.bar#map") + .key(MemberShape.builder() + .id("foo.bar#map$member") + .target(string.getId()) + .build()) + .value(MemberShape.builder() + .id("foo.bar#map$member") + .target(string.getId()) + .build()) + .build(); MemberShape memberForString = MemberShape.builder() - .id("foo.bar#structure$string") - .target(string.getId()) - .build(); + .id("foo.bar#structure$string") + .target(string.getId()) + .build(); + + MemberShape memberForBlob = MemberShape.builder() + .id("foo.bar#blobStructure$blob") + .target(blob.getId()) + .build(); + + MemberShape memberForStreamingBlob = MemberShape.builder() + .id("foo.bar#blobStructure$streamingBlob") + .target(streamingBlob.getId()) + .build(); MemberShape memberForList = MemberShape.builder() - .id("foo.bar#structure$list") - .target(list.getId()) - .build(); + .id("foo.bar#structure$list") + .target(list.getId()) + .build(); MemberShape memberForMap = MemberShape.builder() - .id("foo.bar#structure$map") - .target(map.getId()) - .build(); + .id("foo.bar#structure$map") + .target(map.getId()) + .build(); StructureShape structure = StructureShape.builder() - .id("foo.bar#structure") - .members( - List.of( - memberForString, memberForList, memberForMap, - MemberShape.builder() - .id("foo.bar#structure$list2") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$list3") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$list4") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$list5") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$list6") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$list7") - .target(list.getId()) - .build(), - MemberShape.builder() - .id("foo.bar#structure$structure") - .target("foo.bar#structure") - .build())) - .build(); + .id("foo.bar#structure") + .members( + List.of( + memberForString, memberForList, memberForMap, + MemberShape.builder() + .id("foo.bar#structure$list2") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$list3") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$list4") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$list5") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$list6") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$list7") + .target(list.getId()) + .build(), + MemberShape.builder() + .id("foo.bar#structure$structure") + .target("foo.bar#structure") + .build())) + .build(); + + + StructureShape blobStructure = StructureShape.builder() + .id("foo.bar#blobStructure") + .members( + List.of( + memberForBlob, memberForStreamingBlob + ) + ) + .build(); private Model model = Model.builder() - .addShapes( - string, list, map, structure, - memberForString, memberForList, memberForMap) - .build(); + .addShapes( + string, list, map, structure, + memberForString, memberForList, memberForMap, + blob, streamingBlob + ) + .build(); @Test public void generatesStructuralHintDocumentation_map() { assertThat( - StructureExampleGenerator.generateStructuralHintDocumentation(map, model, false), - equalTo(""" - { // map - "": "STRING_VALUE", - };""")); + StructureExampleGenerator.generateStructuralHintDocumentation(map, model, false, true), + equalTo(""" + { // map + "": "STRING_VALUE", + };""")); } @Test public void generatesStructuralHintDocumentation_structure() { assertThat( - StructureExampleGenerator.generateStructuralHintDocumentation(structure, model, false), - equalTo(""" - { // structure - string: "STRING_VALUE", - list: [ // list - "STRING_VALUE", - ], - map: { // map - "": "STRING_VALUE", - }, - list2: [ - "STRING_VALUE", - ], - list3: [ - "STRING_VALUE", - ], - list4: [ - "STRING_VALUE", - ], - list5: [ - "STRING_VALUE", - ], - list6: "", - list7: "", - structure: { - string: "STRING_VALUE", - list: "", - map: { - "": "STRING_VALUE", - }, - list2: "", - list3: "", - list4: "", - list5: "", - list6: "", - list7: "", - structure: "", - }, - };""")); + StructureExampleGenerator.generateStructuralHintDocumentation(structure, model, false, true), + equalTo(""" + { // structure + string: "STRING_VALUE", + list: [ // list + "STRING_VALUE", + ], + map: { // map + "": "STRING_VALUE", + }, + list2: [ + "STRING_VALUE", + ], + list3: [ + "STRING_VALUE", + ], + list4: [ + "STRING_VALUE", + ], + list5: [ + "STRING_VALUE", + ], + list6: "", + list7: "", + structure: { + string: "STRING_VALUE", + list: "", + map: { + "": "STRING_VALUE", + }, + list2: "", + list3: "", + list4: "", + list5: "", + list6: "", + list7: "", + structure: "", + }, + };""")); } - + @Test public void generatesStructuralHintDocumentation_structure_asComment() { assertThat( - StructureExampleGenerator.generateStructuralHintDocumentation(structure, model, true), - equalTo(""" - // { // structure - // string: "STRING_VALUE", - // list: [ // list - // "STRING_VALUE", - // ], - // map: { // map - // "": "STRING_VALUE", - // }, - // list2: [ - // "STRING_VALUE", - // ], - // list3: [ - // "STRING_VALUE", - // ], - // list4: [ - // "STRING_VALUE", - // ], - // list5: [ - // "STRING_VALUE", - // ], - // list6: "", - // list7: "", - // structure: { - // string: "STRING_VALUE", - // list: "", - // map: { - // "": "STRING_VALUE", - // }, - // list2: "", - // list3: "", - // list4: "", - // list5: "", - // list6: "", - // list7: "", - // structure: "", - // }, - // };""")); + StructureExampleGenerator.generateStructuralHintDocumentation(structure, model, true, true), + equalTo(""" + // { // structure + // string: "STRING_VALUE", + // list: [ // list + // "STRING_VALUE", + // ], + // map: { // map + // "": "STRING_VALUE", + // }, + // list2: [ + // "STRING_VALUE", + // ], + // list3: [ + // "STRING_VALUE", + // ], + // list4: [ + // "STRING_VALUE", + // ], + // list5: [ + // "STRING_VALUE", + // ], + // list6: "", + // list7: "", + // structure: { + // string: "STRING_VALUE", + // list: "", + // map: { + // "": "STRING_VALUE", + // }, + // list2: "", + // list3: "", + // list4: "", + // list5: "", + // list6: "", + // list7: "", + // structure: "", + // }, + // };""")); } @Test public void generatesStructuralHintDocumentation_list() { assertThat( - StructureExampleGenerator.generateStructuralHintDocumentation(list, model, false), - equalTo(""" - [ // list - "STRING_VALUE", - ];""")); + StructureExampleGenerator.generateStructuralHintDocumentation(list, model, false, true), + equalTo(""" + [ // list + "STRING_VALUE", + ];""")); + } + + @Test + public void generateStructuralHintDocumentation_blob() { + assertThat( + StructureExampleGenerator.generateStructuralHintDocumentation(blobStructure, model, false, true), + equalTo(""" + { // blobStructure + blob: new Uint8Array(), // e.g. Buffer.from("") or new TextEncoder().encode("") + streamingBlob: "MULTIPLE_TYPES_ACCEPTED", // see \\@smithy/types -> StreamingBlobPayloadInputTypes + };""")); + assertThat( + StructureExampleGenerator.generateStructuralHintDocumentation(blobStructure, model, false, false), + equalTo(""" + { // blobStructure + blob: new Uint8Array(), + streamingBlob: "", // see \\@smithy/types -> StreamingBlobPayloadOutputTypes + };""")); } }