Skip to content

Commit d12ee97

Browse files
Make streaming blobs required / default
1 parent b28f2d2 commit d12ee97

File tree

21 files changed

+238
-24
lines changed

21 files changed

+238
-24
lines changed

docs/source/1.0/guides/migrating-idl-1-to-2.rst

+54
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,60 @@ Needs to be updated to:
119119
}
120120
121121
122+
Add the default trait to streaming blobs
123+
========================================
124+
125+
Members that target a blob shape with the :ref:`streaming-trait` have always
126+
had an implicit default empty value. In IDL 2.0, that will become explicit.
127+
Any such members that are not already marked with the :ref:`required-trait`
128+
will now need to be marked with the :ref:`default-trait`.
129+
130+
For example, the following model:
131+
132+
.. code-block:: smithy
133+
134+
$version: "1.0"
135+
136+
namespace smithy.example
137+
138+
structure OptionalStream {
139+
// This needs to be updated since it doesn't have the required or
140+
// default trait already.
141+
payload: StreamingBlob
142+
}
143+
144+
structure RequiredStream {
145+
// This doesn't need to be updated because it already has the required
146+
// trait.
147+
@required
148+
payload: StreamingBlob
149+
}
150+
151+
@streaming
152+
blob StreamingBlob
153+
154+
Needs to be updated to:
155+
156+
.. code-block:: smithy
157+
158+
$version: "2.0"
159+
160+
namespace smithy.example
161+
162+
structure OptionalStream {
163+
@default
164+
payload: StreamingBlob
165+
}
166+
167+
structure RequiredStream {
168+
@required
169+
payload: StreamingBlob
170+
}
171+
172+
@streaming
173+
blob StreamingBlob
174+
175+
122176
Optional migration steps
123177
========================
124178

docs/source/1.0/spec/core/traits/behavior-traits.rst.template

+4
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,10 @@ Summary
538538
large and thus should not be stored in memory or that the size is unknown
539539
at the start of the request.
540540

541+
.. warning::
542+
Members targeting streaming blobs MUST be marked with the
543+
:ref:`required-trait` or :ref:`default-trait`.
544+
541545
When applied to a union, it indicates that shape represents an
542546
:ref:`event stream <event-streams>`.
543547
Trait selector::

docs/source/1.0/spec/core/traits/http-traits.rst.template

+1
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@ Trait selector
708708
The ``httpPayload`` trait can be applied to ``structure`` members that
709709
target a ``string``, ``blob``, ``structure``, ``union``, ``document``,
710710
``set``, ``map``, or ``list``.
711+
711712
Value type
712713
Annotation trait.
713714
Conflicts with

smithy-aws-protocol-tests/model/restJson1/services/glacier.smithy

+2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ structure UploadArchiveInput {
206206
archiveDescription: string,
207207
@httpHeader("x-amz-sha256-tree-hash")
208208
checksum: string,
209+
@default
209210
@httpPayload
210211
body: Stream,
211212
}
@@ -224,6 +225,7 @@ structure UploadMultipartPartInput {
224225
checksum: string,
225226
@httpHeader("Content-Range")
226227
range: string,
228+
@default
227229
@httpPayload
228230
body: Stream,
229231
}

smithy-aws-protocol-tests/model/restJson1/streaming.smithy

+3
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ structure StreamingTraitsInputOutput {
9191
@httpHeader("X-Foo")
9292
foo: String,
9393

94+
@default
9495
@httpPayload
9596
blob: StreamingBlob,
9697
}
@@ -151,6 +152,7 @@ structure StreamingTraitsRequireLengthInput {
151152
@httpHeader("X-Foo")
152153
foo: String,
153154

155+
@default
154156
@httpPayload
155157
blob: FiniteStreamingBlob,
156158
}
@@ -212,6 +214,7 @@ structure StreamingTraitsWithMediaTypeInputOutput {
212214
@httpHeader("X-Foo")
213215
foo: String,
214216

217+
@default
215218
@httpPayload
216219
blob: StreamingTextPlainBlob,
217220
}

smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Upgrade1to2Command.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.EnumSet;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.Optional;
3132
import java.util.Set;
3233
import java.util.function.BiConsumer;
3334
import java.util.function.Consumer;
@@ -54,6 +55,7 @@
5455
import software.amazon.smithy.model.shapes.ShapeVisitor;
5556
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer;
5657
import software.amazon.smithy.model.shapes.StringShape;
58+
import software.amazon.smithy.model.traits.AnnotationTrait;
5759
import software.amazon.smithy.model.traits.BoxTrait;
5860
import software.amazon.smithy.model.traits.DefaultTrait;
5961
import software.amazon.smithy.model.traits.EnumTrait;
@@ -313,15 +315,13 @@ private void replacePrimitiveTarget(MemberShape member) {
313315
}
314316

315317
private boolean hasSyntheticDefault(MemberShape shape) {
316-
Shape target = completeModel.expectShape(shape.getTarget());
317-
if (!(HAD_DEFAULT_VALUE_IN_1_0.contains(target.getType()) && shape.hasTrait(DefaultTrait.class))) {
318-
return false;
319-
}
318+
Optional<SourceLocation> defaultLocation = shape.getTrait(DefaultTrait.class)
319+
.map(AnnotationTrait::getSourceLocation);
320320
// When Smithy injects the default trait, it sets the source
321321
// location equal to the shape's source location. This is
322322
// impossible in any other scenario, so we can use this info
323323
// to know whether it was injected or not.
324-
return shape.getSourceLocation().equals(shape.expectTrait(DefaultTrait.class).getSourceLocation());
324+
return defaultLocation.filter(location -> shape.getSourceLocation().equals(location)).isPresent();
325325
}
326326

327327
@Override

smithy-cli/src/test/java/software/amazon/smithy/cli/commands/Upgrade1to2CommandTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void testUpgrade(Path initialPath, String name) {
5757
public static Stream<Arguments> source() throws Exception {
5858
Path start = Paths.get(Upgrade1to2CommandTest.class.getResource("upgrade/cases").toURI());
5959
return Files.walk(start)
60-
.filter(path -> path.getFileName().toString().endsWith("enum-with-traits.v1.smithy"))
60+
.filter(path -> path.getFileName().toString().endsWith(".v1.smithy"))
6161
.map(path -> Arguments.of(path, path.getFileName().toString().replace(".v1.smithy", "")));
6262
}
6363

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
$version: "1.0"
2+
3+
namespace smithy.example
4+
5+
structure RequiredPayload {
6+
@required
7+
payload: StreamingBlob
8+
}
9+
10+
structure DefaultPayload {
11+
@default
12+
payload: StreamingBlob
13+
}
14+
15+
structure NeitherRequiredNorDefaultPayload {
16+
payload: StreamingBlob
17+
}
18+
19+
@streaming
20+
blob StreamingBlob
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
structure RequiredPayload {
6+
@required
7+
payload: StreamingBlob
8+
}
9+
10+
structure DefaultPayload {
11+
@default
12+
payload: StreamingBlob
13+
}
14+
15+
structure NeitherRequiredNorDefaultPayload {
16+
@default
17+
payload: StreamingBlob
18+
}
19+
20+
@streaming
21+
blob StreamingBlob

smithy-model/src/main/java/software/amazon/smithy/model/loader/ModelUpgrader.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import software.amazon.smithy.model.traits.BoxTrait;
3030
import software.amazon.smithy.model.traits.DefaultTrait;
3131
import software.amazon.smithy.model.traits.RequiredTrait;
32+
import software.amazon.smithy.model.traits.StreamingTrait;
3233
import software.amazon.smithy.model.transform.ModelTransformer;
3334
import software.amazon.smithy.model.validation.Severity;
3435
import software.amazon.smithy.model.validation.ValidatedResult;
@@ -134,8 +135,10 @@ private void upgradeV1Member(ShapeType containerType, MemberShape member) {
134135

135136
private boolean shouldV1MemberHaveDefaultTrait(ShapeType containerType, MemberShape member, Shape target) {
136137
return containerType == ShapeType.STRUCTURE
137-
// Only when the targeted shape had a default value by default in v1.
138-
&& HAD_DEFAULT_VALUE_IN_1_0.contains(target.getType())
138+
// Only when the targeted shape had a default value by default in v1 or if
139+
// the member has the http payload trait and targets a streaming blob, which
140+
// implies a default in 2.0
141+
&& (HAD_DEFAULT_VALUE_IN_1_0.contains(target.getType()) || isDefaultPayload(target))
139142
// Not when the targeted shape was one of the prelude types with the @box trait.
140143
// This needs special handling here because the @box trait was removed from these
141144
// prelude shapes in v2.
@@ -150,6 +153,10 @@ private boolean shouldV1MemberHaveDefaultTrait(ShapeType containerType, MemberSh
150153
&& !target.hasTrait(BoxTrait.class);
151154
}
152155

156+
private boolean isDefaultPayload(Shape target) {
157+
return target.hasTrait(StreamingTrait.class) && target.isBlobShape();
158+
}
159+
153160
private MemberShape.Builder createOrReuseBuilder(MemberShape member, MemberShape.Builder builder) {
154161
return builder == null ? member.toBuilder() : builder;
155162
}

smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/StreamingTraitValidator.java

+14
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
import software.amazon.smithy.model.shapes.Shape;
3636
import software.amazon.smithy.model.shapes.ShapeId;
3737
import software.amazon.smithy.model.shapes.UnionShape;
38+
import software.amazon.smithy.model.traits.DefaultTrait;
3839
import software.amazon.smithy.model.traits.HttpPayloadTrait;
3940
import software.amazon.smithy.model.traits.ProtocolDefinitionTrait;
41+
import software.amazon.smithy.model.traits.RequiredTrait;
4042
import software.amazon.smithy.model.traits.RequiresLengthTrait;
4143
import software.amazon.smithy.model.traits.StreamingTrait;
4244
import software.amazon.smithy.model.validation.AbstractValidator;
@@ -63,9 +65,21 @@ public List<ValidationEvent> validate(Model model) {
6365
validateStreamingTargets(model, events);
6466
validateAllEventStreamMembersTargetStructures(model, events);
6567
validateBlobTargetsArePayloads(model, events);
68+
validateBlobTargetsAreNonOptional(model, events);
6669
return events;
6770
}
6871

72+
private void validateBlobTargetsAreNonOptional(Model model, List<ValidationEvent> events) {
73+
for (MemberShape member : model.toSet(MemberShape.class)) {
74+
Shape target = model.expectShape(member.getTarget());
75+
if (target.isBlobShape() && target.hasTrait(StreamingTrait.class)
76+
&& !(member.hasTrait(RequiredTrait.class) || member.hasTrait(DefaultTrait.class))) {
77+
events.add(error(member, "Members that target blobs marked with the `streaming` trait MUST also be "
78+
+ "marked with the `required` or `default` trait."));
79+
}
80+
}
81+
}
82+
6983
private void validateBlobTargetsArePayloads(Model model, List<ValidationEvent> events) {
7084
ServiceIndex serviceIndex = ServiceIndex.of(model);
7185
Set<ServiceShape> servicesWithPayloadSupportingProtocols = new HashSet<>();

smithy-model/src/test/java/software/amazon/smithy/model/knowledge/HttpBindingIndexTest.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import java.util.Map;
2525
import java.util.Optional;
26-
import org.junit.jupiter.api.Assertions;
26+
import org.junit.jupiter.api.BeforeAll;
2727
import org.junit.jupiter.api.Test;
2828
import software.amazon.smithy.model.Model;
2929
import software.amazon.smithy.model.SourceLocation;
@@ -51,10 +51,15 @@
5151

5252
public class HttpBindingIndexTest {
5353

54-
private static Model model = Model.assembler()
55-
.addImport(HttpBindingIndex.class.getResource("http-index.json"))
56-
.assemble()
57-
.unwrap();
54+
private static Model model;
55+
56+
@BeforeAll
57+
private static void readModel() {
58+
model = Model.assembler()
59+
.addImport(HttpBindingIndex.class.getResource("http-index.json"))
60+
.assemble()
61+
.unwrap();
62+
}
5863

5964
@Test
6065
public void providesResponseCode() {

smithy-model/src/test/java/software/amazon/smithy/model/loader/ModelUpgraderTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public void upgradesWhenAllModelsUse1_0() {
4545
assertThat(ShapeId.from("smithy.example#Bytes$nonNull"), not(ShapeMatcher.memberIsNullable(result)));
4646
assertThat(ShapeId.from("smithy.example#Shorts$nonNull"), not(ShapeMatcher.memberIsNullable(result)));
4747
assertThat(ShapeId.from("smithy.example#Integers$nonNull"), addedDefaultTrait(result));
48+
49+
assertThat(ShapeId.from("smithy.example#BlobPayload$payload"), addedDefaultTrait(result));
4850
}
4951

5052
@Test

smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/requiresLength-validator.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
"type": "structure",
1818
"members": {
1919
"Body": {
20-
"target": "ns.foo#StreamingBlob"
20+
"target": "ns.foo#StreamingBlob",
21+
"traits": {
22+
"smithy.api#default": {}
23+
}
2124
}
2225
},
2326
"traits": {
@@ -28,7 +31,10 @@
2831
"type": "structure",
2932
"members": {
3033
"Body": {
31-
"target": "ns.foo#StreamingBlob"
34+
"target": "ns.foo#StreamingBlob",
35+
"traits": {
36+
"smithy.api#default": {}
37+
}
3238
}
3339
},
3440
"traits": {

smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/streaming-blob-without-httppayload.smithy

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ structure StreamingOperationInput {}
2929
structure StreamingOperationOutput {
3030
@required
3131
streamId: String,
32+
33+
@default
3234
output: StreamingBlob,
3335
}
3436

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[ERROR] smithy.example#NeitherRequiredNorDefaultStream$payload: Members that target blobs marked with the `streaming` trait MUST also be marked with the `required` or `default` trait. | StreamingTrait
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
@http(uri: "/", method: "POST")
6+
operation RequiredStreamOperation {
7+
input: RequiredStream
8+
}
9+
10+
structure RequiredStream {
11+
@required
12+
payload: StreamingBlob
13+
}
14+
15+
@http(uri: "/", method: "POST")
16+
operation DefaultStreamOperation {
17+
input: DefaultStream
18+
}
19+
20+
structure DefaultStream {
21+
@default
22+
payload: StreamingBlob
23+
}
24+
25+
@http(uri: "/", method: "POST")
26+
operation NeitherRequiredNorDefaultStreamOperation {
27+
input: NeitherRequiredNorDefaultStream
28+
}
29+
30+
structure NeitherRequiredNorDefaultStream {
31+
payload: StreamingBlob
32+
}
33+
34+
@streaming
35+
blob StreamingBlob

0 commit comments

Comments
 (0)