Skip to content

Commit 3beeac6

Browse files
kuhetrivikr
andauthored
docs: add doc examples to operations (#1078)
* docs: create doc examples from ExamplesTrait in Commands * sort object keys alphabetically * use single line comments for the example description Co-authored-by: Trivikram Kamat <[email protected]> * add DocumentationExampleGenerator unit tests --------- Co-authored-by: Trivikram Kamat <[email protected]>
1 parent 78c1ba0 commit 3beeac6

File tree

3 files changed

+200
-8
lines changed

3 files changed

+200
-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
@@ -39,6 +39,7 @@
3939
import software.amazon.smithy.model.Model;
4040
import software.amazon.smithy.model.knowledge.OperationIndex;
4141
import software.amazon.smithy.model.knowledge.TopDownIndex;
42+
import software.amazon.smithy.model.node.ObjectNode;
4243
import software.amazon.smithy.model.shapes.MemberShape;
4344
import software.amazon.smithy.model.shapes.OperationShape;
4445
import software.amazon.smithy.model.shapes.ServiceShape;
@@ -48,8 +49,10 @@
4849
import software.amazon.smithy.model.traits.DeprecatedTrait;
4950
import software.amazon.smithy.model.traits.DocumentationTrait;
5051
import software.amazon.smithy.model.traits.ErrorTrait;
52+
import software.amazon.smithy.model.traits.ExamplesTrait;
5153
import software.amazon.smithy.model.traits.InternalTrait;
5254
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
55+
import software.amazon.smithy.typescript.codegen.documentation.DocumentationExampleGenerator;
5356
import software.amazon.smithy.typescript.codegen.documentation.StructureExampleGenerator;
5457
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
5558
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
@@ -133,11 +136,13 @@ private void generateClientCommand() {
133136
String name = symbol.getName();
134137

135138
StringBuilder additionalDocs = new StringBuilder()
136-
.append("\n")
137-
.append(getCommandExample(
138-
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
139-
.append("\n")
140-
.append(getThrownExceptions());
139+
.append("\n")
140+
.append(getCommandExample(
141+
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
142+
.append("\n")
143+
.append(getThrownExceptions())
144+
.append("\n")
145+
.append(getCuratedExamples(name));
141146

142147
boolean operationHasDocumentation = operation.hasTrait(DocumentationTrait.class);
143148

@@ -244,10 +249,12 @@ private void generateClientCommand() {
244249
writer.write("}"); // class close bracket.
245250
}
246251

247-
private String getCommandExample(String serviceName, String configName, String commandName, String commandInput,
248-
String commandOutput) {
252+
private String getCommandExample(
253+
String serviceName, String configName, String commandName,
254+
String commandInput, String commandOutput
255+
) {
249256
String packageName = settings.getPackageName();
250-
return "@example\n"
257+
String exampleDoc = "@example\n"
251258
+ "Use a bare-bones client and the command you need to make an API call.\n"
252259
+ "```javascript\n"
253260
+ String.format("import { %s, %s } from \"%s\"; // ES Modules import%n", serviceName, commandName,
@@ -270,6 +277,46 @@ private String getCommandExample(String serviceName, String configName, String c
270277
+ String.format("@see {@link %s} for command's `input` shape.%n", commandInput)
271278
+ String.format("@see {@link %s} for command's `response` shape.%n", commandOutput)
272279
+ String.format("@see {@link %s | config} for %s's `config` shape.%n", configName, serviceName);
280+
281+
return exampleDoc;
282+
}
283+
284+
/**
285+
* Handwritten examples from the operation ExamplesTrait.
286+
*/
287+
private String getCuratedExamples(String commandName) {
288+
String exampleDoc = "";
289+
if (operation.getTrait(ExamplesTrait.class).isPresent()) {
290+
List<ExamplesTrait.Example> examples = operation.getTrait(ExamplesTrait.class).get().getExamples();
291+
StringBuilder buffer = new StringBuilder();
292+
293+
for (ExamplesTrait.Example example : examples) {
294+
ObjectNode input = example.getInput();
295+
Optional<ObjectNode> output = example.getOutput();
296+
buffer
297+
.append("\n")
298+
.append(String.format("@example %s%n", example.getTitle()))
299+
.append("```javascript\n")
300+
.append(String.format("// %s%n", example.getDocumentation().orElse("")))
301+
.append("""
302+
const input = %s;
303+
const command = new %s(input);
304+
const response = await client.send(command);
305+
/* response is
306+
%s
307+
*/
308+
""".formatted(
309+
DocumentationExampleGenerator.inputToJavaScriptObject(input),
310+
commandName,
311+
DocumentationExampleGenerator.outputToJavaScriptObject(output.orElse(null))
312+
))
313+
.append("```")
314+
.append("\n");
315+
}
316+
317+
exampleDoc += buffer.toString();
318+
}
319+
return exampleDoc;
273320
}
274321

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

0 commit comments

Comments
 (0)