Skip to content

Commit

Permalink
Merge pull request #11 from smithy-lang/operations
Browse files Browse the repository at this point in the history
Generate documentation for operations
  • Loading branch information
JordonPhillips authored Nov 6, 2023
2 parents 7936c60 + 494f331 commit 1e78aaa
Show file tree
Hide file tree
Showing 13 changed files with 687 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import software.amazon.smithy.codegen.core.directed.GenerateEnumDirective;
import software.amazon.smithy.codegen.core.directed.GenerateErrorDirective;
import software.amazon.smithy.codegen.core.directed.GenerateIntEnumDirective;
import software.amazon.smithy.codegen.core.directed.GenerateOperationDirective;
import software.amazon.smithy.codegen.core.directed.GenerateResourceDirective;
import software.amazon.smithy.codegen.core.directed.GenerateServiceDirective;
import software.amazon.smithy.codegen.core.directed.GenerateStructureDirective;
import software.amazon.smithy.codegen.core.directed.GenerateUnionDirective;
import software.amazon.smithy.docgen.core.generators.OperationGenerator;
import software.amazon.smithy.docgen.core.generators.ServiceGenerator;
import software.amazon.smithy.docgen.core.generators.StructureGenerator;
import software.amazon.smithy.utils.SmithyUnstableApi;
Expand Down Expand Up @@ -52,6 +54,11 @@ public void generateStructure(GenerateStructureDirective<DocGenerationContext, D
new StructureGenerator().accept(directive);
}

@Override
public void generateOperation(GenerateOperationDirective<DocGenerationContext, DocSettings> directive) {
new OperationGenerator().accept(directive);
}

@Override
public void generateError(GenerateErrorDirective<DocGenerationContext, DocSettings> directive) {
// no-op for now
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@

import static java.lang.String.format;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.OperationIndex;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.ShapeVisitor;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.InputTrait;
import software.amazon.smithy.model.traits.OutputTrait;
import software.amazon.smithy.model.traits.StringTrait;
import software.amazon.smithy.model.traits.TitleTrait;
import software.amazon.smithy.utils.SmithyUnstableApi;
Expand All @@ -35,20 +42,28 @@
* definition section. For services, this defaults to the value of the
* {@code title} trait. For other shapes, it defaults to the shape name including
* any renames from the attached service.
*
* <li>{@code definitionFile}: The file in which the documentation for this shape
* should be written. By default these are all written to a single flat directory.
* If this is empty, the shape does not have its own definition section and cannot
* be linked to.
*
* <li>{@link #SHAPE_PROPERTY}: A named Shape property containing the shape that
* the symbol represents. Decorators provided by
* {@link DocIntegration#decorateSymbolProvider} MUST set or preserve this
* property.
*
* <li>{@link #OPERATION_PROPERTY}: A named OperationShape property containing the
* operation shape that the shape is bound to. This will only be present on
* structure shapes that have the {@code input} or {@code output} traits.
*
* <li>{@link #LINK_ID_PROPERTY}: A named String property containing the string to
* use for the id for links to the shape. In HTML, this would be the {@code id} for
* the tag containing the shape's definition. Given a link id {@code foo}, a link
* to the shape's definition might look like {@code https://example.com/shapes#foo}
* for example. If this or {@code definitionFile} is empty, it is not possible to
* link to the shape.
*
* <li>{@link #ENABLE_DEFAULT_FILE_EXTENSION}: A named boolean property indicating
* whether the symbol's definition file should have the default file extension
* applied. If not present or set to {@code false}, the file extension will not be
Expand All @@ -74,6 +89,17 @@ public final class DocSymbolProvider extends ShapeVisitor.Default<Symbol> implem
*/
public static final String SHAPE_PROPERTY = "shape";

/**
* The operation that the symbol's shape is bound to.
*
* <p>This property will only be present on structures that have either the
* {@code input} or {@code output} trait.
*
* <p>Use {@code symbol.getProperty(OPERATION_PROPERTY, OperationShape.class)} to
* access this property.
*/
public static final String OPERATION_PROPERTY = "operation";

/**
* The name for a shape symbol's named property containing the string to use for
* the id for links to the shape. In HTML, this would be the {@code id} for the tag
Expand Down Expand Up @@ -104,6 +130,7 @@ public final class DocSymbolProvider extends ShapeVisitor.Default<Symbol> implem
private final Model model;
private final DocSettings docSettings;
private final ServiceShape serviceShape;
private final Map<ShapeId, OperationShape> ioToOperation;

/**
* Constructor.
Expand All @@ -115,6 +142,26 @@ public DocSymbolProvider(Model model, DocSettings docSettings) {
this.model = model;
this.docSettings = docSettings;
this.serviceShape = model.expectShape(docSettings.service(), ServiceShape.class);
this.ioToOperation = mapIoShapesToOperations(model);
}

private Map<ShapeId, OperationShape> mapIoShapesToOperations(Model model) {
// Map input and output structures to their containing shapes. These will be
// documented alongside their associated operations, so we need said operations
// when generating symbols for them. Pre-computing this mapping is a bit faster
// than just running a selector every time we hit an IO
// shape.
var operationIoMap = new HashMap<ShapeId, OperationShape>();
var operationIndex = OperationIndex.of(model);
for (var operation : model.getOperationShapes()) {
operationIndex.getInputShape(operation)
.filter(i -> i.hasTrait(InputTrait.class))
.ifPresent(i -> operationIoMap.put(i.getId(), operation));
operationIndex.getOutputShape(operation)
.filter(i -> i.hasTrait(OutputTrait.class))
.ifPresent(i -> operationIoMap.put(i.getId(), operation));
}
return Map.copyOf(operationIoMap);
}

@Override
Expand All @@ -132,12 +179,26 @@ public Symbol serviceShape(ServiceShape shape) {
}

@Override
public Symbol structureShape(StructureShape shape) {
public Symbol operationShape(OperationShape shape) {
return getSymbolBuilder(shape)
.definitionFile(getDefinitionFile(serviceShape, shape))
.build();
}

@Override
public Symbol structureShape(StructureShape shape) {
var builder = getSymbolBuilder(shape);
if (ioToOperation.containsKey(shape.getId())) {
// Input and output structures are documented on the operation's definition page.
var operation = ioToOperation.get(shape.getId());
builder.definitionFile(getDefinitionFile(serviceShape, operation));
builder.putProperty(OPERATION_PROPERTY, operation);
} else {
builder.definitionFile(getDefinitionFile(serviceShape, shape));
}
return builder.build();
}

@Override
public Symbol memberShape(MemberShape shape) {
var builder = getSymbolBuilder(shape)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

package software.amazon.smithy.docgen.core.generators;

import java.util.Collection;
import java.util.Locale;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.docgen.core.DocGenerationContext;
import software.amazon.smithy.docgen.core.DocSymbolProvider;
import software.amazon.smithy.docgen.core.sections.MemberSection;
import software.amazon.smithy.docgen.core.sections.ShapeMembersSection;
import software.amazon.smithy.docgen.core.writers.DocWriter;
Expand Down Expand Up @@ -82,38 +85,69 @@ public MemberGenerator(

@Override
public void run() {
writer.pushState(new ShapeMembersSection(context, shape, shape.members(), listingType));
writer.openHeading(listingType.getTitle());
writer.openMemberListing();
for (MemberShape member: shape.getAllMembers().values()) {
writer.pushState(new MemberSection(context, member));

var symbol = context.symbolProvider().toSymbol(member);
var target = context.model().expectShape(member.getTarget());
writer.openMemberEntry(symbol, w -> target.accept(new MemberTypeVisitor(w, context)));
writer.writeShapeDocs(member, context.model());

writer.closeMemberEntry();
writer.popState();
var members = getMembers();
writer.pushState(new ShapeMembersSection(context, shape, members, listingType));
var parentSymbol = context.symbolProvider().toSymbol(shape);
if (!members.isEmpty()) {
parentSymbol.getProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class).ifPresent(linkId -> {
writer.writeAnchor(linkId + "-" + listingType.getLinkIdSuffix());
});
writer.openHeading(listingType.getTitle());
writer.openMemberListing();
for (MemberShape member : members) {
writer.pushState(new MemberSection(context, member));

var symbol = context.symbolProvider().toSymbol(member);
var target = context.model().expectShape(member.getTarget());
writer.openMemberEntry(symbol, w -> target.accept(new MemberTypeVisitor(w, context)));
writer.writeShapeDocs(member, context.model());

writer.closeMemberEntry();
writer.popState();
}
writer.closeMemberListing();
writer.closeHeading();
}
writer.closeMemberListing();
writer.closeHeading();
writer.popState();
}

private Collection<MemberShape> getMembers() {
return switch (listingType) {
case INPUT -> context.model()
.expectShape(shape.asOperationShape().get().getInputShape())
.getAllMembers().values();
case OUTPUT -> context.model()
.expectShape(shape.asOperationShape().get().getOutputShape())
.getAllMembers().values();
default -> shape.getAllMembers().values();
};
}

/**
* The type of listing. This controls the heading title and anchor id for the section.
*/
public enum MemberListingType {
/**
* Indicates the listing is for normal shape members.
*/
MEMBERS("Members");
MEMBERS("Members"),

/**
* Indicates the listing is for an operation's input members.
*/
INPUT("Request Members"),

/**
* Indicates the listing is for an operation's output members.
*/
OUTPUT("Response Members");

private final String title;
private final String linkIdSuffix;

MemberListingType(String title) {
this.title = title;
this.linkIdSuffix = title.toLowerCase(Locale.ENGLISH).strip().replaceAll("\\s+", "-");
}

/**
Expand All @@ -122,6 +156,14 @@ public enum MemberListingType {
public String getTitle() {
return title;
}

/**
* @return returns the suffix that will be applied to the parent shape's link
* id to form this member listing's link id.
*/
public String getLinkIdSuffix() {
return linkIdSuffix;
}
}

private static class MemberTypeVisitor extends ShapeVisitor.Default<Void> {
Expand Down
Loading

0 comments on commit 1e78aaa

Please sign in to comment.