Skip to content

Commit dc78570

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

File tree

2 files changed

+281
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)