Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add doc examples to operations #1078

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.OperationIndex;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
Expand All @@ -47,8 +48,10 @@
import software.amazon.smithy.model.traits.DeprecatedTrait;
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.model.traits.ExamplesTrait;
import software.amazon.smithy.model.traits.InternalTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
import software.amazon.smithy.typescript.codegen.documentation.DocumentationExampleGenerator;
import software.amazon.smithy.typescript.codegen.documentation.StructureExampleGenerator;
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
Expand Down Expand Up @@ -130,11 +133,13 @@ private void generateClientCommand() {
String name = symbol.getName();

StringBuilder additionalDocs = new StringBuilder()
.append("\n")
.append(getCommandExample(
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
.append("\n")
.append(getThrownExceptions());
.append("\n")
.append(getCommandExample(
serviceSymbol.getName(), configType, name, inputType.getName(), outputType.getName()))
.append("\n")
.append(getThrownExceptions())
.append("\n")
.append(getCuratedExamples(name));

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

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

private String getCommandExample(String serviceName, String configName, String commandName, String commandInput,
String commandOutput) {
private String getCommandExample(
String serviceName, String configName, String commandName,
String commandInput, String commandOutput
) {
String packageName = settings.getPackageName();
return "@example\n"
String exampleDoc = "@example\n"
+ "Use a bare-bones client and the command you need to make an API call.\n"
+ "```javascript\n"
+ String.format("import { %s, %s } from \"%s\"; // ES Modules import%n", serviceName, commandName,
Expand All @@ -225,6 +232,46 @@ private String getCommandExample(String serviceName, String configName, String c
+ String.format("@see {@link %s} for command's `input` shape.%n", commandInput)
+ String.format("@see {@link %s} for command's `response` shape.%n", commandOutput)
+ String.format("@see {@link %s | config} for %s's `config` shape.%n", configName, serviceName);

return exampleDoc;
}

/**
* Handwritten examples from the operation ExamplesTrait.
*/
private String getCuratedExamples(String commandName) {
String exampleDoc = "";
if (operation.getTrait(ExamplesTrait.class).isPresent()) {
List<ExamplesTrait.Example> examples = operation.getTrait(ExamplesTrait.class).get().getExamples();
StringBuilder buffer = new StringBuilder();

for (ExamplesTrait.Example example : examples) {
ObjectNode input = example.getInput();
Optional<ObjectNode> output = example.getOutput();
buffer
.append("\n")
.append(String.format("@example %s%n", example.getTitle()))
.append("```javascript\n")
.append(String.format("// %s%n", example.getDocumentation().orElse("")))
.append("""
const input = %s;
const command = new %s(input);
const response = await client.send(command);
/* response is
%s
*/
""".formatted(
DocumentationExampleGenerator.inputToJavaScriptObject(input),
commandName,
DocumentationExampleGenerator.outputToJavaScriptObject(output.orElse(null))
))
.append("```")
.append("\n");
}

exampleDoc += buffer.toString();
}
return exampleDoc;
}

private String getThrownExceptions() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.typescript.codegen.documentation;

import java.util.Comparator;
import java.util.stream.Collectors;
import software.amazon.smithy.model.node.ArrayNode;
import software.amazon.smithy.model.node.BooleanNode;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NullNode;
import software.amazon.smithy.model.node.NumberNode;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.utils.SmithyInternalApi;

@SmithyInternalApi
public final class DocumentationExampleGenerator {
private DocumentationExampleGenerator() {}

/**
* @return the ObjectNode from the curated example written as a JavaScript object literal.
*/
public static String inputToJavaScriptObject(ObjectNode node) {
if (node == null) {
return "{ /* empty */ }";
}
return write(node, 0);
}

public static String outputToJavaScriptObject(ObjectNode node) {
if (node == null) {
return "{ /* metadata only */ }";
}
return write(node, 0);
}

private static String write(Node node, int indent) {
StringBuilder buffer = new StringBuilder();
String indentation = " ".repeat(indent);

switch (node.getType()) {
case OBJECT -> {
ObjectNode objectNode = node.expectObjectNode();
if (objectNode.getMembers().isEmpty()) {
return indentation + "{ /* empty */ }";
}
String membersJoined = objectNode.getMembers()
.entrySet()
.stream()
.sorted(Comparator.comparing(entry -> entry.getKey().getValue()))
.map(entry -> indentation
+ " "
+ entry.getKey().getValue()
+ ": "
+ write(entry.getValue(), indent + 2))
.collect(Collectors.joining(",\n"));

return buffer
.append("{\n")
.append(membersJoined).append("\n")
.append(indentation).append("}")
.toString();
}
case ARRAY -> {
ArrayNode arrayNode = node.expectArrayNode();
if (arrayNode.getElements().isEmpty()) {
return indentation + "[]";
}
String membersJoined = arrayNode.getElements()
.stream()
.map(elementNode -> indentation
+ " "
+ write(elementNode, indent + 2))
.collect(Collectors.joining(",\n"));

return buffer
.append("[\n")
.append(membersJoined).append("\n")
.append(indentation).append("]")
.toString();
}
case STRING -> {
StringNode stringNode = node.expectStringNode();
return "\"" + stringNode.getValue() + "\"";
}
case NUMBER -> {
NumberNode numberNode = node.expectNumberNode();
return numberNode.getValue().toString();
}
case BOOLEAN -> {
BooleanNode booleanNode = node.expectBooleanNode();
return booleanNode.toString();
}
case NULL -> {
NullNode nullNode = node.expectNullNode();
return nullNode.toString();
}
default -> throw new IllegalStateException("Unexpected value: " + node.getType());
}
}
}
Loading