Skip to content

Commit 9ddf19c

Browse files
committed
Fail to build for services with unsupported payloads
1 parent f8c8a7e commit 9ddf19c

File tree

8 files changed

+2767
-20
lines changed

8 files changed

+2767
-20
lines changed

codegen/src/main/java/software/amazon/awssdk/codegen/IntermediateModelBuilder.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import static software.amazon.awssdk.codegen.RemoveUnusedShapes.removeUnusedShapes;
2020

2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.TreeMap;
28+
import java.util.stream.Collectors;
2729
import org.slf4j.Logger;
2830
import org.slf4j.LoggerFactory;
2931
import software.amazon.awssdk.codegen.customization.CodegenCustomizationProcessor;
@@ -53,6 +55,8 @@
5355
*/
5456
public class IntermediateModelBuilder {
5557

58+
private static final List<String> NO_BODY_METHODS = Arrays.asList("GET", "HEAD", "DELETE");
59+
5660
private static final Logger log = LoggerFactory.getLogger(IntermediateModelBuilder.class);
5761
private final CustomizationConfig customConfig;
5862
private final ServiceModel service;
@@ -157,9 +161,28 @@ public IntermediateModel build() {
157161

158162
setSimpleMethods(trimmedModel);
159163

164+
validateOperations(trimmedModel);
165+
160166
return trimmedModel;
161167
}
162168

169+
/**
170+
* Validates that operations that use GET, DELETE or HEAD do not have an HTTP body.
171+
*/
172+
private void validateOperations(IntermediateModel model) {
173+
List<String> methods = model.getOperations()
174+
.entrySet()
175+
.stream()
176+
.filter(e -> e.getValue().getInputShape().hasPayloadMembers())
177+
.filter(e -> NO_BODY_METHODS.contains(e.getValue().getInputShape().getMarshaller().getVerb()))
178+
.map(e -> e.getValue().getInputShape().getC2jName())
179+
.collect(Collectors.toList());
180+
181+
if (methods.size() > 0) {
182+
throw new RuntimeException("An HTTP body is not allowed on GET/DELETE/HEAD requests. Invalid operations: " + methods);
183+
}
184+
}
185+
163186
/**
164187
* Link the member to it's corresponding shape (if it exists).
165188
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.awssdk.codegen;
16+
17+
import static org.junit.Assert.assertTrue;
18+
import static org.junit.Assert.fail;
19+
20+
import java.io.File;
21+
import org.junit.Test;
22+
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
23+
import software.amazon.awssdk.codegen.model.service.ServiceModel;
24+
import software.amazon.awssdk.codegen.utils.ModelLoaderUtils;
25+
26+
public class IntermediateModelBuilderTest {
27+
28+
@Test(expected = RuntimeException.class)
29+
public void modelWithUnsupportedPayloadMethods_ThrowsException() {
30+
File serviceModel = new File(getClass().getResource("poet/client/c2j/unsupportedpayloads/service-2.json").getFile());
31+
File customizationModel = new File(getClass().getResource("poet/client/c2j/unsupportedpayloads/customization.config").getFile());
32+
33+
C2jModels models = C2jModels.builder()
34+
.serviceModel(ModelLoaderUtils.loadModel(ServiceModel.class, serviceModel))
35+
.customizationConfig(ModelLoaderUtils.loadModel(CustomizationConfig.class, customizationModel))
36+
.build();
37+
38+
try {
39+
new IntermediateModelBuilder(models).build();
40+
} catch (RuntimeException e) {
41+
assertTrue(e.getMessage().contains("GetBlacklistReportsRequest"));
42+
assertTrue(e.getMessage().contains("GetDedicatedIpsRequest"));
43+
throw e;
44+
}
45+
46+
fail("Expected RuntimeException");
47+
}
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"authPolicyActions" : {
3+
"skip" : true
4+
}
5+
}

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/unsupportedpayloads/service-2.json

Lines changed: 2664 additions & 0 deletions
Large diffs are not rendered by default.

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/StructuredJsonGenerator.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ public interface StructuredJsonGenerator {
3232
*/
3333
StructuredJsonGenerator NO_OP = new StructuredJsonGenerator() {
3434

35-
private final byte[] emptyBytes = new byte[0];
36-
3735
@Override
3836
public StructuredJsonGenerator writeStartArray() {
3937
return this;
@@ -126,7 +124,7 @@ public StructuredJsonGenerator writeNumber(String number) {
126124

127125
@Override
128126
public byte[] getBytes() {
129-
return emptyBytes;
127+
return null;
130128
}
131129

132130
@Override

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,12 @@ private SdkHttpFullRequest finishMarshalling() {
196196
}
197197

198198
byte[] content = jsonGenerator.getBytes();
199-
request.contentStreamProvider(() -> new ByteArrayInputStream(content));
200-
if (content.length > 0) {
201-
request.putHeader(CONTENT_LENGTH, Integer.toString(content.length));
199+
200+
if (content != null) {
201+
request.contentStreamProvider(() -> new ByteArrayInputStream(content));
202+
if (content.length > 0) {
203+
request.putHeader(CONTENT_LENGTH, Integer.toString(content.length));
204+
}
202205
}
203206
}
204207

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/handler/BaseClientHandler.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@
1515

1616
package software.amazon.awssdk.core.client.handler;
1717

18+
import static software.amazon.awssdk.http.SdkHttpMethod.DELETE;
19+
import static software.amazon.awssdk.http.SdkHttpMethod.GET;
20+
import static software.amazon.awssdk.http.SdkHttpMethod.HEAD;
21+
1822
import java.net.URI;
23+
import java.util.Arrays;
24+
import java.util.List;
1925
import software.amazon.awssdk.annotations.SdkProtectedApi;
2026
import software.amazon.awssdk.core.SdkRequest;
2127
import software.amazon.awssdk.core.SdkResponse;
28+
import software.amazon.awssdk.core.SdkStandardLogger;
2229
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
2330
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
2431
import software.amazon.awssdk.core.client.config.SdkClientOption;
@@ -29,10 +36,14 @@
2936
import software.amazon.awssdk.core.interceptor.InterceptorContext;
3037
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
3138
import software.amazon.awssdk.http.SdkHttpFullRequest;
39+
import software.amazon.awssdk.http.SdkHttpMethod;
3240
import software.amazon.awssdk.utils.StringUtils;
3341

3442
@SdkProtectedApi
3543
public abstract class BaseClientHandler {
44+
45+
private static final List<SdkHttpMethod> NO_BODY_METHODS = Arrays.asList(GET, DELETE, HEAD);
46+
3647
private SdkClientConfiguration clientConfiguration;
3748

3849
protected BaseClientHandler(SdkClientConfiguration clientConfiguration) {
@@ -64,6 +75,7 @@ static <InputT extends SdkRequest, OutputT> InterceptorContext finalizeSdkHttpFu
6475
request = modifyEndpointHostIfNeeded(request, clientConfiguration, executionParams);
6576

6677
addHttpRequest(executionContext, request);
78+
warnOnUnsupportedRequests(request);
6779
runAfterMarshallingInterceptors(executionContext);
6880
return runModifyHttpRequestAndHttpContentInterceptors(executionContext);
6981
}
@@ -108,6 +120,13 @@ private static SdkHttpFullRequest modifyEndpointHostIfNeeded(SdkHttpFullRequest
108120
.build();
109121
}
110122

123+
private static void warnOnUnsupportedRequests(SdkHttpFullRequest request) {
124+
if (NO_BODY_METHODS.contains(request.method()) && request.contentStreamProvider().isPresent()) {
125+
SdkStandardLogger.REQUEST_LOGGER.warn(() -> NO_BODY_METHODS +
126+
" requests with bodies are not supported by the default SDK clients.");
127+
}
128+
}
129+
111130
private static void addHttpRequest(ExecutionContext executionContext, SdkHttpFullRequest request) {
112131
InterceptorContext interceptorContext = executionContext.interceptorContext().copy(b -> b.httpRequest(request));
113132
executionContext.interceptorContext(interceptorContext);

test/protocol-tests/src/main/resources/codegen-resources/restjson/service-2.json

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,6 @@
4343
"input":{"shape":"FurtherNestedContainersStructure"},
4444
"output":{"shape":"FurtherNestedContainersStructure"}
4545
},
46-
"GetOperationWithBody":{
47-
"name":"GetOperationWithBody",
48-
"http":{
49-
"method":"GET",
50-
"requestUri":"/2016-03-11/getOperationWithBody"
51-
},
52-
"input":{"shape":"GetOperationWithBodyInput"}
53-
},
5446
"HeadOperation":{
5547
"name":"HeadOperation",
5648
"http":{
@@ -303,12 +295,6 @@
303295
"ListOfNested":{"shape":"ListOfNested"}
304296
}
305297
},
306-
"GetOperationWithBodyInput":{
307-
"type":"structure",
308-
"members":{
309-
"StringMember":{"shape":"String"}
310-
}
311-
},
312298
"IdempotentOperationStructure":{
313299
"type":"structure",
314300
"required":["PathIdempotentToken"],

0 commit comments

Comments
 (0)