Skip to content

Commit 0aef2bf

Browse files
committed
docs: create doc examples from ExamplesTrait in Commands
1 parent 38da900 commit 0aef2bf

File tree

2 files changed

+157
-8
lines changed

2 files changed

+157
-8
lines changed

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

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import software.amazon.smithy.model.Model;
3939
import software.amazon.smithy.model.knowledge.OperationIndex;
4040
import software.amazon.smithy.model.knowledge.TopDownIndex;
41+
import software.amazon.smithy.model.node.ObjectNode;
4142
import software.amazon.smithy.model.shapes.MemberShape;
4243
import software.amazon.smithy.model.shapes.OperationShape;
4344
import software.amazon.smithy.model.shapes.ServiceShape;
@@ -47,8 +48,10 @@
4748
import software.amazon.smithy.model.traits.DeprecatedTrait;
4849
import software.amazon.smithy.model.traits.DocumentationTrait;
4950
import software.amazon.smithy.model.traits.ErrorTrait;
51+
import software.amazon.smithy.model.traits.ExamplesTrait;
5052
import software.amazon.smithy.model.traits.InternalTrait;
5153
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
54+
import software.amazon.smithy.typescript.codegen.documentation.DocumentationExampleGenerator;
5255
import software.amazon.smithy.typescript.codegen.documentation.StructureExampleGenerator;
5356
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
5457
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
@@ -130,11 +133,13 @@ private void generateClientCommand() {
130133
String name = symbol.getName();
131134

132135
StringBuilder additionalDocs = new StringBuilder()
133-
.append("\n")
134-
.append(getCommandExample(
135-
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
136-
.append("\n")
137-
.append(getThrownExceptions());
136+
.append("\n")
137+
.append(getCommandExample(
138+
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
139+
.append("\n")
140+
.append(getThrownExceptions())
141+
.append("\n")
142+
.append(getCuratedExamples(name));
138143

139144
boolean operationHasDocumentation = operation.hasTrait(DocumentationTrait.class);
140145

@@ -199,10 +204,12 @@ private void generateClientCommand() {
199204
writer.write("}"); // class close bracket.
200205
}
201206

202-
private String getCommandExample(String serviceName, String configName, String commandName, String commandInput,
203-
String commandOutput) {
207+
private String getCommandExample(
208+
String serviceName, String configName, String commandName,
209+
String commandInput, String commandOutput
210+
) {
204211
String packageName = settings.getPackageName();
205-
return "@example\n"
212+
String exampleDoc = "@example\n"
206213
+ "Use a bare-bones client and the command you need to make an API call.\n"
207214
+ "```javascript\n"
208215
+ String.format("import { %s, %s } from \"%s\"; // ES Modules import%n", serviceName, commandName,
@@ -225,6 +232,46 @@ private String getCommandExample(String serviceName, String configName, String c
225232
+ String.format("@see {@link %s} for command's `input` shape.%n", commandInput)
226233
+ String.format("@see {@link %s} for command's `response` shape.%n", commandOutput)
227234
+ String.format("@see {@link %s | config} for %s's `config` shape.%n", configName, serviceName);
235+
236+
return exampleDoc;
237+
}
238+
239+
/**
240+
* Handwritten examples from the operation ExamplesTrait.
241+
*/
242+
private String getCuratedExamples(String commandName) {
243+
String exampleDoc = "";
244+
if (operation.getTrait(ExamplesTrait.class).isPresent()) {
245+
List<ExamplesTrait.Example> examples = operation.getTrait(ExamplesTrait.class).get().getExamples();
246+
StringBuilder buffer = new StringBuilder();
247+
248+
for (ExamplesTrait.Example example : examples) {
249+
ObjectNode input = example.getInput();
250+
Optional<ObjectNode> output = example.getOutput();
251+
buffer
252+
.append("\n")
253+
.append(String.format("@example %s%n", example.getTitle()))
254+
.append("```javascript\n")
255+
.append(String.format("/* %s */%n", example.getDocumentation().orElse("")))
256+
.append("""
257+
const input = %s;
258+
const command = new %s(input);
259+
const response = await client.send(command);
260+
/* response is
261+
%s
262+
*/
263+
""".formatted(
264+
DocumentationExampleGenerator.inputToJavaScriptObject(input),
265+
commandName,
266+
DocumentationExampleGenerator.outputToJavaScriptObject(output.orElse(null))
267+
))
268+
.append("```")
269+
.append("\n");
270+
}
271+
272+
exampleDoc += buffer.toString();
273+
}
274+
return exampleDoc;
228275
}
229276

230277
private String getThrownExceptions() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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.stream.Collectors;
9+
import software.amazon.smithy.model.node.ArrayNode;
10+
import software.amazon.smithy.model.node.BooleanNode;
11+
import software.amazon.smithy.model.node.Node;
12+
import software.amazon.smithy.model.node.NullNode;
13+
import software.amazon.smithy.model.node.NumberNode;
14+
import software.amazon.smithy.model.node.ObjectNode;
15+
import software.amazon.smithy.model.node.StringNode;
16+
import software.amazon.smithy.utils.SmithyInternalApi;
17+
18+
@SmithyInternalApi
19+
public final class DocumentationExampleGenerator {
20+
private DocumentationExampleGenerator() {}
21+
22+
/**
23+
* @return the ObjectNode from the curated example written as a JavaScript object literal.
24+
*/
25+
public static String inputToJavaScriptObject(ObjectNode node) {
26+
if (node == null) {
27+
return "{ /* empty */ }";
28+
}
29+
return write(node, 0);
30+
}
31+
32+
public static String outputToJavaScriptObject(ObjectNode node) {
33+
if (node == null) {
34+
return "{ /* metadata only */ }";
35+
}
36+
return write(node, 0);
37+
}
38+
39+
private static String write(Node node, int indent) {
40+
StringBuilder buffer = new StringBuilder();
41+
String indentation = " ".repeat(indent);
42+
43+
switch (node.getType()) {
44+
case OBJECT -> {
45+
ObjectNode objectNode = node.expectObjectNode();
46+
if (objectNode.getMembers().isEmpty()) {
47+
return indentation + "{ /* empty */ }";
48+
}
49+
String membersJoined = objectNode.getMembers()
50+
.entrySet()
51+
.stream()
52+
.map(entry -> indentation
53+
+ " "
54+
+ entry.getKey().getValue()
55+
+ ": "
56+
+ write(entry.getValue(), indent + 2))
57+
.collect(Collectors.joining(",\n"));
58+
59+
return buffer
60+
.append("{\n")
61+
.append(membersJoined).append("\n")
62+
.append(indentation).append("}")
63+
.toString();
64+
}
65+
case ARRAY -> {
66+
ArrayNode arrayNode = node.expectArrayNode();
67+
if (arrayNode.getElements().isEmpty()) {
68+
return indentation + "[]";
69+
}
70+
String membersJoined = arrayNode.getElements()
71+
.stream()
72+
.map(elementNode -> indentation
73+
+ " "
74+
+ write(elementNode, indent + 2))
75+
.collect(Collectors.joining(",\n"));
76+
77+
return buffer
78+
.append("[\n")
79+
.append(membersJoined).append("\n")
80+
.append(indentation).append("]")
81+
.toString();
82+
}
83+
case STRING -> {
84+
StringNode stringNode = node.expectStringNode();
85+
return "\"" + stringNode.getValue() + "\"";
86+
}
87+
case NUMBER -> {
88+
NumberNode numberNode = node.expectNumberNode();
89+
return numberNode.getValue().toString();
90+
}
91+
case BOOLEAN -> {
92+
BooleanNode booleanNode = node.expectBooleanNode();
93+
return booleanNode.toString();
94+
}
95+
case NULL -> {
96+
NullNode nullNode = node.expectNullNode();
97+
return nullNode.toString();
98+
}
99+
default -> throw new IllegalStateException("Unexpected value: " + node.getType());
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)