Skip to content

Commit

Permalink
add structural hint to command examples
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Mar 24, 2023
1 parent 0d4eb34 commit dc78570
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.ErrorTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
import software.amazon.smithy.typescript.codegen.documentation.StructureExampleGenerator;
import software.amazon.smithy.typescript.codegen.endpointsV2.EndpointsParamNameMap;
import software.amazon.smithy.typescript.codegen.endpointsV2.RuleSetParameterFinder;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
Expand Down Expand Up @@ -174,6 +175,9 @@ private String getCommandExample(String serviceName, String configName, String c
+ String.format("// const { %s, %s } = require(\"%s\"); // CommonJS import%n", serviceName, commandName,
packageName)
+ String.format("const client = new %s(config);%n", serviceName)
+ String.format("const input = %s%n",
StructureExampleGenerator.generateStructuralHintDocumentation(
model.getShape(operation.getInputShape()).get(), model))
+ String.format("const command = new %s(input);%n", commandName)
+ "const response = await client.send(command);\n"
+ "```\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/*
* 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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.EnumShape;
import software.amazon.smithy.model.shapes.IntEnumShape;
import software.amazon.smithy.model.shapes.ListShape;
import software.amazon.smithy.model.shapes.MapShape;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.UnionShape;
import software.amazon.smithy.model.traits.RequiredTrait;

/**
* Generates a structural hint for a shape used in command documentation.
*/
public abstract class StructureExampleGenerator {
/**
* Generates an example structure for API documentation, as an
* automated gap filler for operations that do not have
* hand written examples.
*
* Example for Athena::createPreparedStatement
* ```js
* const input = {
* // QueryStatement: 'STRING_VALUE', // required
* // StatementName: 'STRING_VALUE', // required
* // WorkGroup: 'STRING_VALUE', // required
* // Description: 'STRING_VALUE'
* };
* ```
*/
public static String generateStructuralHintDocumentation(Shape shape, Model model) {
StringBuilder buffer = new StringBuilder();
shape(shape, buffer, model, 0, new ShapeTracker());

// replace non-leading whitespace with single space.
String s = Arrays.stream(
buffer.toString()
.split("\n"))
.map(line -> line.replaceAll(
"([\\w\\\",:] )\\s+",
"$1"))
.collect(Collectors.joining("\n"));

return s;
}

private static void structure(StructureShape structureShape,
StringBuilder buffer, Model model,
int indentation,
ShapeTracker shapeTracker) {
if (structureShape.getAllMembers().size() == 0) {
append(indentation, buffer, "{}");
checkRequired(indentation, buffer, structureShape);
} else {
append(indentation, buffer, "{");
checkRequired(indentation, buffer, structureShape);
structureShape.getAllMembers().values().forEach(member -> {
append(indentation + 2, buffer, member.getMemberName() + ": ");
shape(member, buffer, model, indentation + 2, shapeTracker);
});
append(indentation, buffer, "}");
}
}

private static void union(UnionShape unionShape,
StringBuilder buffer,
Model model,
int indentation,
ShapeTracker shapeTracker) {
append(indentation, buffer, "{ // Union: only one key present");
checkRequired(indentation, buffer, unionShape);
unionShape.getAllMembers().values().forEach(member -> {
append(indentation + 2, buffer, member.getMemberName() + ": ");
shape(member, buffer, model, indentation + 2, shapeTracker);
});
append(indentation, buffer, "}");
}

private static void shape(Shape shape,
StringBuilder buffer,
Model model,
int indentation,
ShapeTracker shapeTracker) {
Shape target;
if (shape instanceof MemberShape) {
target = model.getShape(((MemberShape) shape).getTarget()).get();
} else {
target = shape;
}

shapeTracker.mark(shape, indentation);
if (shapeTracker.getOccurrenceDepths(shape) > 5) {
append(indentation, buffer, "\"<" + shape.getId().getName() + ">\"");
} else {
switch (target.getType()) {
case BIG_DECIMAL:
append(indentation, buffer, "Number('bigdecimal'),");
break;
case BIG_INTEGER:
append(indentation, buffer, "Number('bigint'),");
break;
case BLOB:
append(indentation, buffer, "\"BLOB_VALUE\",");
break;
case BOOLEAN:
append(indentation, buffer, "true || false,");
break;
case BYTE:
append(indentation, buffer, "\"BYTE_VALUE\",");
break;
case DOCUMENT:
append(indentation, buffer, "\"DOCUMENT_VALUE\",");
break;
case DOUBLE:
append(indentation, buffer, "Number('double'),");
break;
case FLOAT:
append(indentation, buffer, "Number('float'),");
break;
case INTEGER:
append(indentation, buffer, "Number('int'),");
break;
case LONG:
append(indentation, buffer, "Number('long'),");
break;
case SHORT:
append(indentation, buffer, "Number('short'),");
break;
case STRING:
append(indentation, buffer, "\"STRING_VALUE\",");
break;
case TIMESTAMP:
append(indentation, buffer, "\"TIMESTAMP\",");
break;

case SET:
case LIST:
append(indentation, buffer, "[");
checkRequired(indentation, buffer, shape);
ListShape list = (ListShape) target;
shape(list.getMember(), buffer, model, indentation + 2, shapeTracker);
append(indentation, buffer, "],");
break;
case MAP:
append(indentation, buffer, "{");
checkRequired(indentation, buffer, shape);
append(indentation + 2, buffer, "\"<keys>\": ");
MapShape map = (MapShape) target;
shape(model.getShape(map.getValue().getTarget()).get(), buffer, model, indentation + 2,
shapeTracker);
append(indentation, buffer, "},");
break;

case STRUCTURE:
StructureShape structure = (StructureShape) target;
structure(structure, buffer, model, indentation, shapeTracker);
break;
case UNION:
UnionShape union = (UnionShape) target;
union(union, buffer, model, indentation, shapeTracker);
break;

case ENUM:
EnumShape enumShape = (EnumShape) target;
String enumeration = enumShape.getEnumValues()
.values()
.stream()
.map(s -> "\"" + s + "\"")
.collect(Collectors.joining(" || "));
append(indentation, buffer, enumeration);
break;
case INT_ENUM:
IntEnumShape intEnumShape = (IntEnumShape) target;
String intEnumeration = intEnumShape.getEnumValues()
.values()
.stream()
.map(s -> "\"" + s + "\"")
.collect(Collectors.joining(" || "));
append(indentation, buffer, intEnumeration);
break;
case OPERATION:
case RESOURCE:
case SERVICE:
case MEMBER:
default:
append(indentation, buffer, "\"...\",");
break;
}
}

switch (target.getType()) {
case STRUCTURE:
case UNION:
case LIST:
case SET:
case MAP:
break;
case BIG_DECIMAL:
case BIG_INTEGER:
case BLOB:
case BOOLEAN:
case BYTE:
case DOCUMENT:
case DOUBLE:
case ENUM:
case FLOAT:
case INTEGER:
case INT_ENUM:
case LONG:
case MEMBER:
case OPERATION:
case RESOURCE:
case SERVICE:
case SHORT:
case STRING:
case TIMESTAMP:
default:
checkRequired(indentation, buffer, shape);
break;
}
}

private static void checkRequired(int indentation, StringBuilder buffer, Shape shape) {
if (shape.hasTrait(RequiredTrait.class)) {
append(indentation, buffer, " // required\n");
} else {
append(indentation, buffer, "\n");
}
}

private static void append(int indentation, StringBuilder buffer, String tail) {
while (indentation-- > 0) {
buffer.append(" ");
}
buffer.append(tail);
}

/**
* Tracks the depths at which a shape appears in the tree.
* If a shape appears at too many depths it is truncated.
* This handles the case of recursive shapes.
*/
private static class ShapeTracker {
private Map<Shape, Set<Integer>> data = new HashMap<Shape, Set<Integer>>();

/**
* Mark that a shape is observed at depth.
*/
public void mark(Shape shape, int depth) {
if (!data.containsKey(shape)) {
data.put(shape, new HashSet<>());
}
data.get(shape).add(depth);
}

/**
* @return the number of distinct depths in which the shape appears.
*/
public int getOccurrenceDepths(Shape shape) {
return data.getOrDefault(shape, Collections.emptySet()).size();
}
}
}

0 comments on commit dc78570

Please sign in to comment.