Skip to content

Commit ab6fdaf

Browse files
Add idempotency information
1 parent 957632c commit ab6fdaf

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/integrations/BuiltinsIntegration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import software.amazon.smithy.docgen.core.interceptors.DeprecatedInterceptor;
1515
import software.amazon.smithy.docgen.core.interceptors.ErrorFaultInterceptor;
1616
import software.amazon.smithy.docgen.core.interceptors.ExternalDocsInterceptor;
17+
import software.amazon.smithy.docgen.core.interceptors.IdempotencyInterceptor;
1718
import software.amazon.smithy.docgen.core.interceptors.InternalInterceptor;
1819
import software.amazon.smithy.docgen.core.interceptors.LengthInterceptor;
1920
import software.amazon.smithy.docgen.core.interceptors.NoReplaceBindingInterceptor;
@@ -76,6 +77,7 @@ public List<? extends CodeInterceptor<? extends CodeSection, DocWriter>> interce
7677
new RangeInterceptor(),
7778
new LengthInterceptor(),
7879
new ExternalDocsInterceptor(),
80+
new IdempotencyInterceptor(),
7981
new ErrorFaultInterceptor(),
8082
new DefaultValueInterceptor(),
8183
new SinceInterceptor(),
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.docgen.core.interceptors;
7+
8+
import java.util.Optional;
9+
import software.amazon.smithy.codegen.core.SymbolReference;
10+
import software.amazon.smithy.docgen.core.sections.ShapeDetailsSection;
11+
import software.amazon.smithy.docgen.core.writers.DocWriter;
12+
import software.amazon.smithy.docgen.core.writers.DocWriter.AdmonitionType;
13+
import software.amazon.smithy.model.Model;
14+
import software.amazon.smithy.model.knowledge.OperationIndex;
15+
import software.amazon.smithy.model.shapes.MemberShape;
16+
import software.amazon.smithy.model.shapes.OperationShape;
17+
import software.amazon.smithy.model.traits.IdempotencyTokenTrait;
18+
import software.amazon.smithy.model.traits.IdempotentTrait;
19+
import software.amazon.smithy.model.traits.ReadonlyTrait;
20+
import software.amazon.smithy.utils.CodeInterceptor;
21+
import software.amazon.smithy.utils.Pair;
22+
import software.amazon.smithy.utils.SmithyInternalApi;
23+
24+
/**
25+
* Provides information about idempotency depending on a number of traits.
26+
*/
27+
@SmithyInternalApi
28+
public final class IdempotencyInterceptor implements CodeInterceptor<ShapeDetailsSection, DocWriter> {
29+
private static final Pair<String, String> IDEMPOTENT_REF = Pair.of(
30+
"idempotent", "https://datatracker.ietf.org/doc/html/rfc7231.html#section-4.2.2"
31+
);
32+
private static final Pair<String, String> UUID_REF = Pair.of(
33+
"UUID", "https://tools.ietf.org/html/rfc4122.html"
34+
);
35+
36+
@Override
37+
public Class<ShapeDetailsSection> sectionType() {
38+
return ShapeDetailsSection.class;
39+
}
40+
41+
@Override
42+
public boolean isIntercepted(ShapeDetailsSection section) {
43+
var shape = section.shape();
44+
var model = section.context().model();
45+
var operationIndex = OperationIndex.of(model);
46+
47+
if (shape.hasTrait(IdempotencyTokenTrait.class)
48+
&& operationIndex.isInputStructure(shape.asMemberShape().get().getContainer())) {
49+
return true;
50+
}
51+
52+
var target = shape.isMemberShape()
53+
? model.expectShape(shape.asMemberShape().get().getTarget())
54+
: shape;
55+
56+
if (!target.isOperationShape()) {
57+
return false;
58+
}
59+
60+
return shape.getMemberTrait(model, IdempotentTrait.class).isPresent()
61+
|| shape.getMemberTrait(model, ReadonlyTrait.class).isPresent()
62+
|| getIdempotencyToken(model, target.asOperationShape().get()).isPresent();
63+
}
64+
65+
private Optional<MemberShape> getIdempotencyToken(Model model, OperationShape operation) {
66+
var input = model.expectShape(operation.getInputShape());
67+
for (var member : input.members()) {
68+
if (member.hasTrait(IdempotencyTokenTrait.class)) {
69+
return Optional.of(member);
70+
}
71+
}
72+
return Optional.empty();
73+
}
74+
75+
@Override
76+
public void write(DocWriter writer, String previousText, ShapeDetailsSection section) {
77+
if (section.shape().isMemberShape()) {
78+
writer.openAdmonition(AdmonitionType.NOTE);
79+
writer.write("""
80+
This value will be used by the service to ensure the request is $R. \
81+
Clients SHOULD automatically populate this (typically with a $R) if \
82+
it was not explicitly set.
83+
84+
""", IDEMPOTENT_REF, UUID_REF);
85+
writer.closeAdmonition();
86+
writer.writeWithNoFormatting(previousText);
87+
return;
88+
}
89+
90+
var operation = section.shape().asOperationShape().get();
91+
var idempotencyToken = getIdempotencyToken(section.context().model(), operation)
92+
.map(member -> section.context().symbolProvider().toSymbol(member))
93+
.map(symbol -> SymbolReference.builder()
94+
.alias(String.format("idempotency token (%s)", symbol.getName()))
95+
.symbol(symbol)
96+
.build());
97+
writer.putContext("token", idempotencyToken);
98+
writer.openAdmonition(AdmonitionType.NOTE);
99+
writer.write("""
100+
This operation is $R${?token} when the ${token:R} is set${/token}.
101+
102+
""", IDEMPOTENT_REF);
103+
writer.closeAdmonition();
104+
writer.writeWithNoFormatting(previousText);
105+
}
106+
}

smithy-docgen-test/model/main.smithy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,22 @@ service DocumentedService {
4747
)
4848
operation DocumentedOperation {
4949
input := {
50+
/// This is an idempotency token, which will inherently mark this operation
51+
/// as idempotent.
52+
@idempotencyToken
53+
token: String
54+
5055
structure: DocumentedStructure
56+
5157
lengthExamples: LengthTraitExamples
58+
5259
rangeExamples: RangeTraitExamples
5360
}
61+
5462
output := {
5563
structure: DocumentedStructure
5664
}
65+
5766
errors: [
5867
DocumentedOperationError
5968
]

0 commit comments

Comments
 (0)