Skip to content

Commit 02ceef1

Browse files
authored
add structural hint to command examples (#723)
* add structural hint to command examples * formatting fixes * timestamp format * use java text blocks
1 parent 1f4623d commit 02ceef1

File tree

3 files changed

+387
-0
lines changed

3 files changed

+387
-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,283 @@
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.IntEnumShape;
18+
import software.amazon.smithy.model.shapes.ListShape;
19+
import software.amazon.smithy.model.shapes.MapShape;
20+
import software.amazon.smithy.model.shapes.MemberShape;
21+
import software.amazon.smithy.model.shapes.Shape;
22+
import software.amazon.smithy.model.shapes.StructureShape;
23+
import software.amazon.smithy.model.shapes.UnionShape;
24+
import software.amazon.smithy.model.traits.RequiredTrait;
25+
import software.amazon.smithy.model.traits.StreamingTrait;
26+
27+
/**
28+
* Generates a structural hint for a shape used in command documentation.
29+
*/
30+
public abstract class StructureExampleGenerator {
31+
/**
32+
* Generates an example structure for API documentation, as an
33+
* automated gap filler for operations that do not have
34+
* hand written examples.
35+
*
36+
* Example for Athena::createPreparedStatement
37+
* ```js
38+
* const input = {
39+
* // QueryStatement: 'STRING_VALUE', // required
40+
* // StatementName: 'STRING_VALUE', // required
41+
* // WorkGroup: 'STRING_VALUE', // required
42+
* // Description: 'STRING_VALUE'
43+
* };
44+
* ```
45+
*/
46+
public static String generateStructuralHintDocumentation(Shape shape, Model model) {
47+
StringBuilder buffer = new StringBuilder();
48+
shape(shape, buffer, model, 0, new ShapeTracker());
49+
50+
// replace non-leading whitespace with single space.
51+
String s = Arrays.stream(
52+
buffer.toString()
53+
.split("\n"))
54+
.map(line -> line.replaceAll(
55+
"([\\w\\\",:\\[\\{] )\\s+",
56+
"$1")
57+
.replaceAll("\\s+$", ""))
58+
.collect(Collectors.joining("\n"));
59+
60+
return s.replaceAll(",$", ";");
61+
}
62+
63+
private static void structure(StructureShape structureShape,
64+
StringBuilder buffer, Model model,
65+
int indentation,
66+
ShapeTracker shapeTracker) {
67+
if (structureShape.getAllMembers().size() == 0) {
68+
append(indentation, buffer, "{},");
69+
checkRequired(indentation, buffer, structureShape);
70+
} else {
71+
append(indentation, buffer, "{");
72+
checkRequired(indentation, buffer, structureShape);
73+
structureShape.getAllMembers().values().forEach(member -> {
74+
append(indentation + 2, buffer, member.getMemberName() + ": ");
75+
shape(member, buffer, model, indentation + 2, shapeTracker);
76+
});
77+
append(indentation, buffer, "},\n");
78+
}
79+
}
80+
81+
private static void union(UnionShape unionShape,
82+
StringBuilder buffer,
83+
Model model,
84+
int indentation,
85+
ShapeTracker shapeTracker) {
86+
append(indentation, buffer, "{ // Union: only one key present");
87+
checkRequired(indentation, buffer, unionShape);
88+
unionShape.getAllMembers().values().forEach(member -> {
89+
append(indentation + 2, buffer, member.getMemberName() + ": ");
90+
shape(member, buffer, model, indentation + 2, shapeTracker);
91+
});
92+
append(indentation, buffer, "},\n");
93+
}
94+
95+
private static void shape(Shape shape,
96+
StringBuilder buffer,
97+
Model model,
98+
int indentation,
99+
ShapeTracker shapeTracker) {
100+
Shape target;
101+
if (shape instanceof MemberShape) {
102+
target = model.getShape(((MemberShape) shape).getTarget()).get();
103+
} else {
104+
target = shape;
105+
}
106+
107+
shapeTracker.mark(shape, indentation);
108+
if (shapeTracker.getOccurrenceDepths(shape) > 2) {
109+
append(indentation, buffer, "\"<" + shape.getId().getName() + ">\",\n");
110+
} else {
111+
switch (target.getType()) {
112+
case BIG_DECIMAL:
113+
append(indentation, buffer, "Number(\"bigdecimal\"),");
114+
break;
115+
case BIG_INTEGER:
116+
append(indentation, buffer, "Number(\"bigint\"),");
117+
break;
118+
case BLOB:
119+
if (target.hasTrait(StreamingTrait.class)) {
120+
append(indentation, buffer, "\"STREAMING_BLOB_VALUE\",");
121+
} else {
122+
append(indentation, buffer, "\"BLOB_VALUE\",");
123+
}
124+
break;
125+
case BOOLEAN:
126+
append(indentation, buffer, "true || false,");
127+
break;
128+
case BYTE:
129+
append(indentation, buffer, "\"BYTE_VALUE\",");
130+
break;
131+
case DOCUMENT:
132+
append(indentation, buffer, "\"DOCUMENT_VALUE\",");
133+
break;
134+
case DOUBLE:
135+
append(indentation, buffer, "Number(\"double\"),");
136+
break;
137+
case FLOAT:
138+
append(indentation, buffer, "Number(\"float\"),");
139+
break;
140+
case INTEGER:
141+
append(indentation, buffer, "Number(\"int\"),");
142+
break;
143+
case LONG:
144+
append(indentation, buffer, "Number(\"long\"),");
145+
break;
146+
case SHORT:
147+
append(indentation, buffer, "Number(\"short\"),");
148+
break;
149+
case STRING:
150+
append(indentation, buffer, "\"STRING_VALUE\",");
151+
break;
152+
case TIMESTAMP:
153+
append(indentation, buffer, "new Date(\"TIMESTAMP\"),");
154+
break;
155+
156+
case SET:
157+
case LIST:
158+
append(indentation, buffer, "[");
159+
checkRequired(indentation, buffer, shape);
160+
ListShape list = (ListShape) target;
161+
shape(list.getMember(), buffer, model, indentation + 2, shapeTracker);
162+
append(indentation, buffer, "],\n");
163+
break;
164+
case MAP:
165+
append(indentation, buffer, "{");
166+
checkRequired(indentation, buffer, shape);
167+
append(indentation + 2, buffer, "\"<keys>\": ");
168+
MapShape map = (MapShape) target;
169+
shape(model.getShape(map.getValue().getTarget()).get(), buffer, model, indentation + 2,
170+
shapeTracker);
171+
append(indentation, buffer, "},\n");
172+
break;
173+
174+
case STRUCTURE:
175+
StructureShape structure = (StructureShape) target;
176+
structure(structure, buffer, model, indentation, shapeTracker);
177+
break;
178+
case UNION:
179+
UnionShape union = (UnionShape) target;
180+
union(union, buffer, model, indentation, shapeTracker);
181+
break;
182+
183+
case ENUM:
184+
EnumShape enumShape = (EnumShape) target;
185+
String enumeration = enumShape.getEnumValues()
186+
.values()
187+
.stream()
188+
.map(s -> "\"" + s + "\"")
189+
.collect(Collectors.joining(" || "));
190+
append(indentation, buffer, enumeration + ",");
191+
break;
192+
case INT_ENUM:
193+
IntEnumShape intEnumShape = (IntEnumShape) target;
194+
String intEnumeration = intEnumShape.getEnumValues()
195+
.values()
196+
.stream()
197+
.map(i -> Integer.toString(i))
198+
.collect(Collectors.joining(" || "));
199+
append(indentation, buffer, intEnumeration + ",");
200+
break;
201+
case OPERATION:
202+
case RESOURCE:
203+
case SERVICE:
204+
case MEMBER:
205+
default:
206+
append(indentation, buffer, "\"...\",");
207+
break;
208+
}
209+
210+
switch (target.getType()) {
211+
case STRUCTURE:
212+
case UNION:
213+
case LIST:
214+
case SET:
215+
case MAP:
216+
break;
217+
case BIG_DECIMAL:
218+
case BIG_INTEGER:
219+
case BLOB:
220+
case BOOLEAN:
221+
case BYTE:
222+
case DOCUMENT:
223+
case DOUBLE:
224+
case ENUM:
225+
case FLOAT:
226+
case INTEGER:
227+
case INT_ENUM:
228+
case LONG:
229+
case MEMBER:
230+
case OPERATION:
231+
case RESOURCE:
232+
case SERVICE:
233+
case SHORT:
234+
case STRING:
235+
case TIMESTAMP:
236+
default:
237+
checkRequired(indentation, buffer, shape);
238+
break;
239+
}
240+
}
241+
}
242+
243+
private static void checkRequired(int indentation, StringBuilder buffer, Shape shape) {
244+
if (shape.hasTrait(RequiredTrait.class)) {
245+
append(indentation, buffer, " // required\n");
246+
} else {
247+
append(indentation, buffer, "\n");
248+
}
249+
}
250+
251+
private static void append(int indentation, StringBuilder buffer, String tail) {
252+
while (indentation-- > 0) {
253+
buffer.append(" ");
254+
}
255+
buffer.append(tail);
256+
}
257+
258+
/**
259+
* Tracks the depths at which a shape appears in the tree.
260+
* If a shape appears at too many depths it is truncated.
261+
* This handles the case of recursive shapes.
262+
*/
263+
private static class ShapeTracker {
264+
private Map<Shape, Set<Integer>> data = new HashMap<Shape, Set<Integer>>();
265+
266+
/**
267+
* Mark that a shape is observed at depth.
268+
*/
269+
public void mark(Shape shape, int depth) {
270+
if (!data.containsKey(shape)) {
271+
data.put(shape, new HashSet<>());
272+
}
273+
data.get(shape).add(depth);
274+
}
275+
276+
/**
277+
* @return the number of distinct depths in which the shape appears.
278+
*/
279+
public int getOccurrenceDepths(Shape shape) {
280+
return data.getOrDefault(shape, Collections.emptySet()).size();
281+
}
282+
}
283+
}

0 commit comments

Comments
 (0)