Skip to content

Commit 494f331

Browse files
Generate documentation for operations
1 parent 7936c60 commit 494f331

File tree

13 files changed

+687
-40
lines changed

13 files changed

+687
-40
lines changed

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DirectedDocGen.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
import software.amazon.smithy.codegen.core.directed.GenerateEnumDirective;
1313
import software.amazon.smithy.codegen.core.directed.GenerateErrorDirective;
1414
import software.amazon.smithy.codegen.core.directed.GenerateIntEnumDirective;
15+
import software.amazon.smithy.codegen.core.directed.GenerateOperationDirective;
1516
import software.amazon.smithy.codegen.core.directed.GenerateResourceDirective;
1617
import software.amazon.smithy.codegen.core.directed.GenerateServiceDirective;
1718
import software.amazon.smithy.codegen.core.directed.GenerateStructureDirective;
1819
import software.amazon.smithy.codegen.core.directed.GenerateUnionDirective;
20+
import software.amazon.smithy.docgen.core.generators.OperationGenerator;
1921
import software.amazon.smithy.docgen.core.generators.ServiceGenerator;
2022
import software.amazon.smithy.docgen.core.generators.StructureGenerator;
2123
import software.amazon.smithy.utils.SmithyUnstableApi;
@@ -52,6 +54,11 @@ public void generateStructure(GenerateStructureDirective<DocGenerationContext, D
5254
new StructureGenerator().accept(directive);
5355
}
5456

57+
@Override
58+
public void generateOperation(GenerateOperationDirective<DocGenerationContext, DocSettings> directive) {
59+
new OperationGenerator().accept(directive);
60+
}
61+
5562
@Override
5663
public void generateError(GenerateErrorDirective<DocGenerationContext, DocSettings> directive) {
5764
// no-op for now

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocSymbolProvider.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,25 @@
77

88
import static java.lang.String.format;
99

10+
import java.util.HashMap;
1011
import java.util.Locale;
12+
import java.util.Map;
1113
import java.util.Objects;
1214
import java.util.Optional;
1315
import java.util.logging.Logger;
1416
import software.amazon.smithy.codegen.core.Symbol;
1517
import software.amazon.smithy.codegen.core.SymbolProvider;
1618
import software.amazon.smithy.model.Model;
19+
import software.amazon.smithy.model.knowledge.OperationIndex;
1720
import software.amazon.smithy.model.shapes.MemberShape;
21+
import software.amazon.smithy.model.shapes.OperationShape;
1822
import software.amazon.smithy.model.shapes.ServiceShape;
1923
import software.amazon.smithy.model.shapes.Shape;
24+
import software.amazon.smithy.model.shapes.ShapeId;
2025
import software.amazon.smithy.model.shapes.ShapeVisitor;
2126
import software.amazon.smithy.model.shapes.StructureShape;
27+
import software.amazon.smithy.model.traits.InputTrait;
28+
import software.amazon.smithy.model.traits.OutputTrait;
2229
import software.amazon.smithy.model.traits.StringTrait;
2330
import software.amazon.smithy.model.traits.TitleTrait;
2431
import software.amazon.smithy.utils.SmithyUnstableApi;
@@ -35,20 +42,28 @@
3542
* definition section. For services, this defaults to the value of the
3643
* {@code title} trait. For other shapes, it defaults to the shape name including
3744
* any renames from the attached service.
45+
*
3846
* <li>{@code definitionFile}: The file in which the documentation for this shape
3947
* should be written. By default these are all written to a single flat directory.
4048
* If this is empty, the shape does not have its own definition section and cannot
4149
* be linked to.
50+
*
4251
* <li>{@link #SHAPE_PROPERTY}: A named Shape property containing the shape that
4352
* the symbol represents. Decorators provided by
4453
* {@link DocIntegration#decorateSymbolProvider} MUST set or preserve this
4554
* property.
55+
*
56+
* <li>{@link #OPERATION_PROPERTY}: A named OperationShape property containing the
57+
* operation shape that the shape is bound to. This will only be present on
58+
* structure shapes that have the {@code input} or {@code output} traits.
59+
*
4660
* <li>{@link #LINK_ID_PROPERTY}: A named String property containing the string to
4761
* use for the id for links to the shape. In HTML, this would be the {@code id} for
4862
* the tag containing the shape's definition. Given a link id {@code foo}, a link
4963
* to the shape's definition might look like {@code https://example.com/shapes#foo}
5064
* for example. If this or {@code definitionFile} is empty, it is not possible to
5165
* link to the shape.
66+
*
5267
* <li>{@link #ENABLE_DEFAULT_FILE_EXTENSION}: A named boolean property indicating
5368
* whether the symbol's definition file should have the default file extension
5469
* applied. If not present or set to {@code false}, the file extension will not be
@@ -74,6 +89,17 @@ public final class DocSymbolProvider extends ShapeVisitor.Default<Symbol> implem
7489
*/
7590
public static final String SHAPE_PROPERTY = "shape";
7691

92+
/**
93+
* The operation that the symbol's shape is bound to.
94+
*
95+
* <p>This property will only be present on structures that have either the
96+
* {@code input} or {@code output} trait.
97+
*
98+
* <p>Use {@code symbol.getProperty(OPERATION_PROPERTY, OperationShape.class)} to
99+
* access this property.
100+
*/
101+
public static final String OPERATION_PROPERTY = "operation";
102+
77103
/**
78104
* The name for a shape symbol's named property containing the string to use for
79105
* the id for links to the shape. In HTML, this would be the {@code id} for the tag
@@ -104,6 +130,7 @@ public final class DocSymbolProvider extends ShapeVisitor.Default<Symbol> implem
104130
private final Model model;
105131
private final DocSettings docSettings;
106132
private final ServiceShape serviceShape;
133+
private final Map<ShapeId, OperationShape> ioToOperation;
107134

108135
/**
109136
* Constructor.
@@ -115,6 +142,26 @@ public DocSymbolProvider(Model model, DocSettings docSettings) {
115142
this.model = model;
116143
this.docSettings = docSettings;
117144
this.serviceShape = model.expectShape(docSettings.service(), ServiceShape.class);
145+
this.ioToOperation = mapIoShapesToOperations(model);
146+
}
147+
148+
private Map<ShapeId, OperationShape> mapIoShapesToOperations(Model model) {
149+
// Map input and output structures to their containing shapes. These will be
150+
// documented alongside their associated operations, so we need said operations
151+
// when generating symbols for them. Pre-computing this mapping is a bit faster
152+
// than just running a selector every time we hit an IO
153+
// shape.
154+
var operationIoMap = new HashMap<ShapeId, OperationShape>();
155+
var operationIndex = OperationIndex.of(model);
156+
for (var operation : model.getOperationShapes()) {
157+
operationIndex.getInputShape(operation)
158+
.filter(i -> i.hasTrait(InputTrait.class))
159+
.ifPresent(i -> operationIoMap.put(i.getId(), operation));
160+
operationIndex.getOutputShape(operation)
161+
.filter(i -> i.hasTrait(OutputTrait.class))
162+
.ifPresent(i -> operationIoMap.put(i.getId(), operation));
163+
}
164+
return Map.copyOf(operationIoMap);
118165
}
119166

120167
@Override
@@ -132,12 +179,26 @@ public Symbol serviceShape(ServiceShape shape) {
132179
}
133180

134181
@Override
135-
public Symbol structureShape(StructureShape shape) {
182+
public Symbol operationShape(OperationShape shape) {
136183
return getSymbolBuilder(shape)
137184
.definitionFile(getDefinitionFile(serviceShape, shape))
138185
.build();
139186
}
140187

188+
@Override
189+
public Symbol structureShape(StructureShape shape) {
190+
var builder = getSymbolBuilder(shape);
191+
if (ioToOperation.containsKey(shape.getId())) {
192+
// Input and output structures are documented on the operation's definition page.
193+
var operation = ioToOperation.get(shape.getId());
194+
builder.definitionFile(getDefinitionFile(serviceShape, operation));
195+
builder.putProperty(OPERATION_PROPERTY, operation);
196+
} else {
197+
builder.definitionFile(getDefinitionFile(serviceShape, shape));
198+
}
199+
return builder.build();
200+
}
201+
141202
@Override
142203
public Symbol memberShape(MemberShape shape) {
143204
var builder = getSymbolBuilder(shape)

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/MemberGenerator.java

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

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

8+
import java.util.Collection;
9+
import java.util.Locale;
810
import software.amazon.smithy.codegen.core.CodegenException;
911
import software.amazon.smithy.docgen.core.DocGenerationContext;
12+
import software.amazon.smithy.docgen.core.DocSymbolProvider;
1013
import software.amazon.smithy.docgen.core.sections.MemberSection;
1114
import software.amazon.smithy.docgen.core.sections.ShapeMembersSection;
1215
import software.amazon.smithy.docgen.core.writers.DocWriter;
@@ -82,38 +85,69 @@ public MemberGenerator(
8285

8386
@Override
8487
public void run() {
85-
writer.pushState(new ShapeMembersSection(context, shape, shape.members(), listingType));
86-
writer.openHeading(listingType.getTitle());
87-
writer.openMemberListing();
88-
for (MemberShape member: shape.getAllMembers().values()) {
89-
writer.pushState(new MemberSection(context, member));
90-
91-
var symbol = context.symbolProvider().toSymbol(member);
92-
var target = context.model().expectShape(member.getTarget());
93-
writer.openMemberEntry(symbol, w -> target.accept(new MemberTypeVisitor(w, context)));
94-
writer.writeShapeDocs(member, context.model());
95-
96-
writer.closeMemberEntry();
97-
writer.popState();
88+
var members = getMembers();
89+
writer.pushState(new ShapeMembersSection(context, shape, members, listingType));
90+
var parentSymbol = context.symbolProvider().toSymbol(shape);
91+
if (!members.isEmpty()) {
92+
parentSymbol.getProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class).ifPresent(linkId -> {
93+
writer.writeAnchor(linkId + "-" + listingType.getLinkIdSuffix());
94+
});
95+
writer.openHeading(listingType.getTitle());
96+
writer.openMemberListing();
97+
for (MemberShape member : members) {
98+
writer.pushState(new MemberSection(context, member));
99+
100+
var symbol = context.symbolProvider().toSymbol(member);
101+
var target = context.model().expectShape(member.getTarget());
102+
writer.openMemberEntry(symbol, w -> target.accept(new MemberTypeVisitor(w, context)));
103+
writer.writeShapeDocs(member, context.model());
104+
105+
writer.closeMemberEntry();
106+
writer.popState();
107+
}
108+
writer.closeMemberListing();
109+
writer.closeHeading();
98110
}
99-
writer.closeMemberListing();
100-
writer.closeHeading();
101111
writer.popState();
102112
}
103113

114+
private Collection<MemberShape> getMembers() {
115+
return switch (listingType) {
116+
case INPUT -> context.model()
117+
.expectShape(shape.asOperationShape().get().getInputShape())
118+
.getAllMembers().values();
119+
case OUTPUT -> context.model()
120+
.expectShape(shape.asOperationShape().get().getOutputShape())
121+
.getAllMembers().values();
122+
default -> shape.getAllMembers().values();
123+
};
124+
}
125+
104126
/**
105127
* The type of listing. This controls the heading title and anchor id for the section.
106128
*/
107129
public enum MemberListingType {
108130
/**
109131
* Indicates the listing is for normal shape members.
110132
*/
111-
MEMBERS("Members");
133+
MEMBERS("Members"),
134+
135+
/**
136+
* Indicates the listing is for an operation's input members.
137+
*/
138+
INPUT("Request Members"),
139+
140+
/**
141+
* Indicates the listing is for an operation's output members.
142+
*/
143+
OUTPUT("Response Members");
112144

113145
private final String title;
146+
private final String linkIdSuffix;
114147

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

119153
/**
@@ -122,6 +156,14 @@ public enum MemberListingType {
122156
public String getTitle() {
123157
return title;
124158
}
159+
160+
/**
161+
* @return returns the suffix that will be applied to the parent shape's link
162+
* id to form this member listing's link id.
163+
*/
164+
public String getLinkIdSuffix() {
165+
return linkIdSuffix;
166+
}
125167
}
126168

127169
private static class MemberTypeVisitor extends ShapeVisitor.Default<Void> {

0 commit comments

Comments
 (0)