Skip to content

Commit e77c271

Browse files
committed
add structural hint to command examples
1 parent 0d4eb34 commit e77c271

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import software.amazon.smithy.model.traits.DocumentationTrait;
4343
import software.amazon.smithy.model.traits.ErrorTrait;
4444
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
45+
import software.amazon.smithy.typescript.codegen.documentation.StructureExampleGenerator;
4546
import software.amazon.smithy.typescript.codegen.endpointsV2.EndpointsParamNameMap;
4647
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
4748
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
@@ -174,6 +175,9 @@ private String getCommandExample(String serviceName, String configName, String c
174175
+ String.format("// const { %s, %s } = require(\"%s\"); // CommonJS import%n", serviceName, commandName,
175176
packageName)
176177
+ String.format("const client = new %s(config);%n", serviceName)
178+
+ String.format("const input = %s%n",
179+
StructureExampleGenerator.generateStructuralHintDocumentation(
180+
model.getShape(operation.getInputShape()).get(), model))
177181
+ String.format("const command = new %s(input);%n", commandName)
178182
+ "const response = await client.send(command);\n"
179183
+ "```\n"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.typescript.codegen.documentation;
7+
8+
import java.util.Arrays;
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.HashSet;
12+
import java.util.Map;
13+
import java.util.Set;
14+
import java.util.stream.Collectors;
15+
import software.amazon.smithy.model.Model;
16+
import software.amazon.smithy.model.shapes.EnumShape;
17+
import software.amazon.smithy.model.shapes.ListShape;
18+
import software.amazon.smithy.model.shapes.MapShape;
19+
import software.amazon.smithy.model.shapes.MemberShape;
20+
import software.amazon.smithy.model.shapes.Shape;
21+
import software.amazon.smithy.model.shapes.StructureShape;
22+
import software.amazon.smithy.model.shapes.UnionShape;
23+
import software.amazon.smithy.model.traits.RequiredTrait;
24+
25+
/**
26+
* Generates a structural hint for a shape used in command documentation.
27+
*/
28+
public abstract class StructureExampleGenerator {
29+
/**
30+
* Generates an example structure for API documentation, as an
31+
* automated gap filler for operations that do not have
32+
* hand written examples.
33+
*
34+
* Example for Athena::createPreparedStatement
35+
* ```js
36+
* const input = {
37+
* // QueryStatement: 'STRING_VALUE', // required
38+
* // StatementName: 'STRING_VALUE', // required
39+
* // WorkGroup: 'STRING_VALUE', // required
40+
* // Description: 'STRING_VALUE'
41+
* };
42+
* ```
43+
*/
44+
public static String generateStructuralHintDocumentation(Shape shape, Model model) {
45+
StringBuilder buffer = new StringBuilder();
46+
shape(shape, buffer, model, 0, new ShapeTracker());
47+
48+
// replace non-leading whitespace with single space.
49+
String s = Arrays.stream(
50+
buffer.toString()
51+
.split("\n"))
52+
.map(line -> line.replaceAll(
53+
"([\\w\\\",:] )\\s+",
54+
"$1"))
55+
.collect(Collectors.joining("\n"));
56+
57+
return s;
58+
}
59+
60+
private static void structure(StructureShape structureShape,
61+
StringBuilder buffer, Model model,
62+
int indentation,
63+
ShapeTracker shapeTracker) {
64+
append(indentation, buffer, "{\n");
65+
structureShape.getAllMembers().values().forEach(member -> {
66+
append(indentation + 2, buffer, member.getMemberName() + ": ");
67+
shape(member, buffer, model, indentation + 2, shapeTracker);
68+
});
69+
append(indentation, buffer, "}");
70+
}
71+
72+
private static void union(UnionShape unionShape,
73+
StringBuilder buffer,
74+
Model model,
75+
int indentation,
76+
ShapeTracker shapeTracker) {
77+
append(indentation, buffer, "{ // Union: only one key present \n");
78+
unionShape.getAllMembers().values().forEach(member -> {
79+
append(indentation + 2, buffer, member.getMemberName() + ": ");
80+
shape(member, buffer, model, indentation + 2, shapeTracker);
81+
});
82+
append(indentation, buffer, "}");
83+
}
84+
85+
private static void shape(Shape shape,
86+
StringBuilder buffer,
87+
Model model,
88+
int indentation,
89+
ShapeTracker shapeTracker) {
90+
Shape target;
91+
if (shape instanceof MemberShape) {
92+
target = model.getShape(((MemberShape) shape).getTarget()).get();
93+
} else {
94+
target = shape;
95+
}
96+
97+
shapeTracker.mark(shape, indentation);
98+
if (shapeTracker.getOccurrenceDepths(shape) > 5) {
99+
append(indentation, buffer, "\"<" + shape.getId().getName() + ">\"");
100+
} else {
101+
switch (target.getType()) {
102+
case BIG_DECIMAL:
103+
append(indentation, buffer, "Number('bigdecimal'),");
104+
break;
105+
case BIG_INTEGER:
106+
append(indentation, buffer, "Number('bigint'),");
107+
break;
108+
case BLOB:
109+
append(indentation, buffer, "\"BLOB_VALUE\",");
110+
break;
111+
case BOOLEAN:
112+
append(indentation, buffer, "true || false,");
113+
break;
114+
case BYTE:
115+
append(indentation, buffer, "\"BYTE_VALUE\",");
116+
break;
117+
case DOCUMENT:
118+
append(indentation, buffer, "\"DOCUMENT_VALUE\",");
119+
break;
120+
case DOUBLE:
121+
append(indentation, buffer, "Number('double'),");
122+
break;
123+
case FLOAT:
124+
append(indentation, buffer, "Number('float'),");
125+
break;
126+
case INTEGER:
127+
append(indentation, buffer, "Number('int'),");
128+
break;
129+
case LONG:
130+
append(indentation, buffer, "Number('long'),");
131+
break;
132+
case SHORT:
133+
append(indentation, buffer, "Number('short'),");
134+
break;
135+
case STRING:
136+
append(indentation, buffer, "\"STRING_VALUE\",");
137+
break;
138+
case TIMESTAMP:
139+
append(indentation, buffer, "\"TIMESTAMP\",");
140+
break;
141+
142+
case SET:
143+
case LIST:
144+
append(indentation, buffer, "[\n");
145+
ListShape list = (ListShape) target;
146+
shape(list.getMember(), buffer, model, indentation + 2, shapeTracker);
147+
append(indentation, buffer, "],");
148+
break;
149+
case MAP:
150+
append(indentation, buffer, "{\n");
151+
append(indentation + 2, buffer, "\"<keys>\": ");
152+
MapShape map = (MapShape) target;
153+
shape(model.getShape(map.getValue().getTarget()).get(), buffer, model, indentation + 2,
154+
shapeTracker);
155+
append(indentation, buffer, "},");
156+
break;
157+
158+
case STRUCTURE:
159+
StructureShape structure = (StructureShape) target;
160+
structure(structure, buffer, model, indentation, shapeTracker);
161+
break;
162+
case UNION:
163+
UnionShape union = (UnionShape) target;
164+
union(union, buffer, model, indentation, shapeTracker);
165+
break;
166+
167+
case ENUM:
168+
EnumShape enumShape = (EnumShape) target;
169+
String enumeration = enumShape.getEnumValues()
170+
.values()
171+
.stream()
172+
.map(s -> "\"" + s + "\"")
173+
.collect(Collectors.joining(" || "));
174+
append(indentation, buffer, enumeration);
175+
break;
176+
case INT_ENUM:
177+
case OPERATION:
178+
case RESOURCE:
179+
case SERVICE:
180+
case MEMBER:
181+
default:
182+
append(indentation, buffer, "\"...\",");
183+
break;
184+
}
185+
}
186+
187+
if (shape.hasTrait(RequiredTrait.class)) {
188+
append(indentation, buffer, "// required\n");
189+
} else {
190+
append(indentation, buffer, "\n");
191+
}
192+
}
193+
194+
private static void append(int indentation, StringBuilder buffer, String tail) {
195+
while (indentation-- > 0) {
196+
buffer.append(" ");
197+
}
198+
buffer.append(tail);
199+
}
200+
201+
/**
202+
* Tracks the depths at which a shape appears in the tree.
203+
* If a shape appears at too many depths it is truncated.
204+
* This handles the case of recursive shapes.
205+
*/
206+
private static class ShapeTracker {
207+
private Map<Shape, Set<Integer>> data = new HashMap<Shape, Set<Integer>>();
208+
209+
/**
210+
* Mark that a shape is observed at depth.
211+
*/
212+
public void mark(Shape shape, int depth) {
213+
if (!data.containsKey(shape)) {
214+
data.put(shape, new HashSet<>());
215+
}
216+
data.get(shape).add(depth);
217+
}
218+
219+
/**
220+
* @return the number of distinct depths in which the shape appears.
221+
*/
222+
public int getOccurrenceDepths(Shape shape) {
223+
return data.getOrDefault(shape, Collections.emptySet()).size();
224+
}
225+
}
226+
}

0 commit comments

Comments
 (0)