Skip to content

Commit 97b6ddd

Browse files
committed
REST-JSON: Unmarshall URI members from body
When unmarshalling, shapes that have marshalling locations set for a URI component such as path or query paramter, they are located in the payload on responses.
1 parent 5b6db7d commit 97b6ddd

File tree

7 files changed

+91
-46
lines changed

7 files changed

+91
-46
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"description": "For JSON protocols, when unmarshalling a response, if a member is declared to be located in the URI, the member is treated as being located in the payload instead."
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 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+
16+
package software.amazon.awssdk.protocols.json.internal;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
import software.amazon.awssdk.core.protocol.MarshallLocation;
20+
21+
@SdkInternalApi
22+
public final class MarshallerUtil {
23+
private MarshallerUtil() {
24+
}
25+
26+
/**
27+
* @return true if the location is in the URI, false otherwise.
28+
*/
29+
public static boolean locationInUri(MarshallLocation location) {
30+
switch (location) {
31+
case PATH:
32+
case QUERY_PARAM:
33+
return true;
34+
default:
35+
return false;
36+
}
37+
}
38+
}

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonProtocolUnmarshaller.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import software.amazon.awssdk.http.SdkHttpFullResponse;
3939
import software.amazon.awssdk.protocols.core.StringToInstant;
4040
import software.amazon.awssdk.protocols.core.StringToValueConverter;
41+
import software.amazon.awssdk.protocols.json.internal.MarshallerUtil;
4142
import software.amazon.awssdk.protocols.json.internal.dom.JsonDomParser;
4243
import software.amazon.awssdk.protocols.json.internal.dom.SdkJsonNode;
4344
import software.amazon.awssdk.utils.builder.Buildable;
@@ -163,7 +164,7 @@ public T unmarshall(JsonUnmarshallerContext context,
163164

164165
public <TypeT extends SdkPojo> TypeT unmarshall(SdkPojo sdkPojo,
165166
SdkHttpFullResponse response) throws IOException {
166-
if (hasPayloadMembers(sdkPojo) && !hasExplicitBlobPayloadMember(sdkPojo)) {
167+
if (hasPayloadMembersOnUnmarshall(sdkPojo) && !hasExplicitBlobPayloadMember(sdkPojo)) {
167168
SdkJsonNode jsonNode = parser.parse(ReleasableInputStream.wrap(response.content().orElse(null)).disableClose());
168169
return unmarshall(sdkPojo, response, jsonNode);
169170
} else {
@@ -181,10 +182,11 @@ private static boolean isExplicitPayloadMember(SdkField<?> f) {
181182
return f.containsTrait(PayloadTrait.class);
182183
}
183184

184-
private boolean hasPayloadMembers(SdkPojo sdkPojo) {
185+
private boolean hasPayloadMembersOnUnmarshall(SdkPojo sdkPojo) {
185186
return sdkPojo.sdkFields()
186-
.stream()
187-
.anyMatch(f -> f.location() == MarshallLocation.PAYLOAD);
187+
.stream()
188+
.anyMatch(f -> f.location() == MarshallLocation.PAYLOAD
189+
|| MarshallerUtil.locationInUri(f.location()));
188190
}
189191

190192
public <TypeT extends SdkPojo> TypeT unmarshall(SdkPojo sdkPojo,

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/unmarshall/JsonUnmarshallerContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.core.protocol.MarshallLocation;
2121
import software.amazon.awssdk.core.protocol.MarshallingType;
2222
import software.amazon.awssdk.http.SdkHttpFullResponse;
23+
import software.amazon.awssdk.protocols.json.internal.MarshallerUtil;
2324

2425
/**
2526
* Dependencies needed by implementations of {@link JsonUnmarshaller}.
@@ -51,6 +52,11 @@ public SdkHttpFullResponse response() {
5152
* @throws SdkClientException if no unmarshaller is found.
5253
*/
5354
public JsonUnmarshaller<Object> getUnmarshaller(MarshallLocation location, MarshallingType<?> marshallingType) {
55+
// A member being in the URI on a response is nonsensical; when a member is declared to be somewhere in the URI,
56+
// it should be found in the payload on response
57+
if (MarshallerUtil.locationInUri(location)) {
58+
location = MarshallLocation.PAYLOAD;
59+
}
5460
return unmarshallerRegistry.getUnmarshaller(location, marshallingType);
5561
}
5662

test/protocol-tests-core/src/main/java/software/amazon/awssdk/protocol/runners/UnmarshallingTestRunner.java

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
2121
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
2222

23+
import com.fasterxml.jackson.databind.JsonNode;
2324
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
2425
import com.github.tomakehurst.wiremock.client.WireMock;
25-
import java.lang.reflect.Method;
2626
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
2727
import software.amazon.awssdk.codegen.model.intermediate.Metadata;
2828
import software.amazon.awssdk.core.sync.ResponseTransformer;
@@ -31,6 +31,7 @@
3131
import software.amazon.awssdk.protocol.model.GivenResponse;
3232
import software.amazon.awssdk.protocol.model.TestCase;
3333
import software.amazon.awssdk.protocol.reflect.ClientReflector;
34+
import software.amazon.awssdk.protocol.reflect.ShapeModelReflector;
3435
import software.amazon.awssdk.utils.IoUtils;
3536

3637
/**
@@ -51,13 +52,14 @@ class UnmarshallingTestRunner {
5152
void runTest(TestCase testCase) throws Exception {
5253
resetWireMock(testCase.getGiven().getResponse());
5354
String operationName = testCase.getWhen().getOperationName();
55+
ShapeModelReflector shapeModelReflector = createShapeModelReflector(testCase);
5456
if (!hasStreamingMember(operationName)) {
55-
Object actualResult = clientReflector.invokeMethod(testCase, createRequestObject(operationName));
57+
Object actualResult = clientReflector.invokeMethod(testCase, shapeModelReflector.createShapeObject());
5658
testCase.getThen().getUnmarshallingAssertion().assertMatches(createContext(operationName), actualResult);
5759
} else {
5860
CapturingResponseTransformer responseHandler = new CapturingResponseTransformer();
5961
Object actualResult = clientReflector
60-
.invokeStreamingMethod(testCase, createRequestObject(operationName), responseHandler);
62+
.invokeStreamingMethod(testCase, shapeModelReflector.createShapeObject(), responseHandler);
6163
testCase.getThen().getUnmarshallingAssertion()
6264
.assertMatches(createContext(operationName, responseHandler.captured), actualResult);
6365
}
@@ -112,35 +114,11 @@ private ResponseDefinitionBuilder toResponseBuilder(GivenResponse givenResponse)
112114
return responseBuilder;
113115
}
114116

115-
/**
116-
* @return An empty request object to call the operation method with.
117-
*/
118-
private Object createRequestObject(String operationName) throws Exception {
119-
String requestClassName = getModelFqcn(getOperationRequestClassName(operationName));
120-
121-
Class<?> requestClass = Class.forName(requestClassName);
122-
123-
Method builderMethod = null;
124-
125-
try {
126-
builderMethod = requestClass.getDeclaredMethod("builder");
127-
} catch (NoSuchMethodException ignored) {
128-
// Ignored
129-
}
130-
131-
if (builderMethod != null) {
132-
builderMethod.setAccessible(true);
133-
134-
Object builderInstance = builderMethod.invoke(null);
135-
136-
Method buildMethod = builderInstance.getClass().getDeclaredMethod("build");
137-
buildMethod.setAccessible(true);
138-
139-
return buildMethod.invoke(builderInstance);
140-
} else {
141-
return requestClass.newInstance();
142-
}
143-
117+
private ShapeModelReflector createShapeModelReflector(TestCase testCase) {
118+
String operationName = testCase.getWhen().getOperationName();
119+
String requestClassName = getOperationRequestClassName(operationName);
120+
JsonNode input = testCase.getGiven().getInput();
121+
return new ShapeModelReflector(model, requestClassName, input);
144122
}
145123

146124
private UnmarshallingTestContext createContext(String operationName) {
@@ -154,14 +132,6 @@ private UnmarshallingTestContext createContext(String operationName, String stre
154132
.withStreamedResponse(streamedResponse);
155133
}
156134

157-
/**
158-
* @param simpleClassName Class name to fully qualify.
159-
* @return Fully qualified name of class in the client's model package.
160-
*/
161-
private String getModelFqcn(String simpleClassName) {
162-
return String.format("%s.%s", metadata.getFullModelPackageName(), simpleClassName);
163-
}
164-
165135
/**
166136
* @return Name of the request class that corresponds to the given operation.
167137
*/

test/protocol-tests-core/src/main/resources/software/amazon/awssdk/protocol/suites/cases/rest-json-output.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,27 @@
114114
"StatusCodeMember": 201
115115
}
116116
}
117+
},
118+
{
119+
"description": "Members declared to be in the query parameters or path on response are unmarshalled from the payload",
120+
"given": {
121+
"input": {
122+
"PathParam": "param"
123+
},
124+
"response": {
125+
"status_code": 200,
126+
"body": "{\"PathParam\": \"Found PathParam in the payload! Yay!\", \"QueryParamOne\":\"Found QueryParamOne in the payload! Yay!\"}"
127+
}
128+
},
129+
"when": {
130+
"action": "unmarshall",
131+
"operation": "MultiLocationOperation"
132+
},
133+
"then": {
134+
"deserializedAs": {
135+
"PathParam": "Found PathParam in the payload! Yay!",
136+
"QueryParamOne": "Found QueryParamOne in the payload! Yay!"
137+
}
138+
}
117139
}
118140
]

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,17 @@
101101
"method":"GET",
102102
"requestUri":"/2016-03-11/membersInQueryParams?StaticQueryParam=foo"
103103
},
104-
"input":{"shape":"MembersInQueryParamsInput"}
104+
"input":{"shape":"MembersInQueryParamsInput"},
105+
"output":{"shape":"MembersInQueryParamsInput"}
105106
},
106107
"MultiLocationOperation":{
107108
"name":"MultiLocationOperation",
108109
"http":{
109110
"method":"POST",
110111
"requestUri":"/2016-03-11/multiLocationOperation/{PathParam}"
111112
},
112-
"input":{"shape":"MultiLocationOperationInput"}
113+
"input":{"shape":"MultiLocationOperationInput"},
114+
"output":{"shape":"MultiLocationOperationInput"}
113115
},
114116
"NestedContainers":{
115117
"name":"NestedContainers",

0 commit comments

Comments
 (0)