diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java index 19600e33210e..a965a12f5f29 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddShapes.java @@ -22,9 +22,11 @@ import static software.amazon.awssdk.codegen.internal.Utils.isScalar; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import software.amazon.awssdk.codegen.internal.TypeUtils; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.intermediate.EnumModel; @@ -36,6 +38,7 @@ import software.amazon.awssdk.codegen.model.intermediate.ReturnTypeModel; import software.amazon.awssdk.codegen.model.intermediate.ShapeModel; import software.amazon.awssdk.codegen.model.intermediate.VariableModel; +import software.amazon.awssdk.codegen.model.service.ErrorMap; import software.amazon.awssdk.codegen.model.service.Location; import software.amazon.awssdk.codegen.model.service.Member; import software.amazon.awssdk.codegen.model.service.Operation; @@ -54,6 +57,7 @@ abstract class AddShapes { private final IntermediateModelBuilder builder; private final NamingStrategy namingStrategy; + private Set directOperationShapes; AddShapes(IntermediateModelBuilder builder) { this.builder = builder; @@ -311,8 +315,13 @@ private ParameterHttpMapping generateParameterHttpMapping(Shape parentShape, ParameterHttpMapping mapping = new ParameterHttpMapping(); + // https://smithy.io/2.0/spec/http-bindings.html + Location location = isDirectOperationShape(parentShape, allC2jShapes) + ? Location.forValue(member.getLocation()) + : null; + Shape memberShape = allC2jShapes.get(member.getShape()); - mapping.withLocation(Location.forValue(member.getLocation())) + mapping.withLocation(location) .withPayload(member.isPayload()).withStreaming(member.isStreaming()) .withFlattened(isFlattened(member, memberShape)) .withUnmarshallLocationName(deriveUnmarshallerLocationName(memberShape, memberName, member)) @@ -323,6 +332,26 @@ private ParameterHttpMapping generateParameterHttpMapping(Shape parentShape, return mapping; } + private boolean isDirectOperationShape(Shape parentShape, Map allC2jShapes) { + if (directOperationShapes == null) { + directOperationShapes = new HashSet<>(); + for (Operation operation : builder.getService().getOperations().values()) { + if (operation.getInput() != null) { + directOperationShapes.add(allC2jShapes.get(operation.getInput().getShape())); + } + if (operation.getOutput() != null) { + directOperationShapes.add(allC2jShapes.get(operation.getOutput().getShape())); + } + if (operation.getErrors() != null) { + for (ErrorMap error : operation.getErrors()) { + directOperationShapes.add(allC2jShapes.get(error.getShape())); + } + } + } + } + return directOperationShapes.contains(parentShape); + } + private boolean isFlattened(Member member, Shape memberShape) { return member.isFlattened() || memberShape.isFlattened(); @@ -342,9 +371,9 @@ private boolean isRequiredMember(String memberName, Shape memberShape) { */ private boolean isGreedy(Shape parentShape, Map allC2jShapes, ParameterHttpMapping mapping) { if (mapping.getLocation() == Location.URI) { - // If the location is URI we can assume the parent shape is an input shape. - String requestUri = findRequestUri(parentShape, allC2jShapes); - if (requestUri.contains(String.format("{%s+}", mapping.getMarshallLocationName()))) { + Optional requestUri = findRequestUri(parentShape, allC2jShapes); + if (requestUri.isPresent() + && requestUri.get().contains(String.format("{%s+}", mapping.getMarshallLocationName()))) { return true; } } @@ -352,28 +381,42 @@ private boolean isGreedy(Shape parentShape, Map allC2jShapes, Par } /** - * Given an input shape, finds the Request URI for the operation that input is referenced from. + * Given a shape, finds the Request URI for the operation that references it as input. + * Returns empty if the shape is not a direct operation input. * - * @param parentShape Input shape to find operation's request URI for. + * @param parentShape Shape to find operation's request URI for. * @param allC2jShapes All shapes in the service model. - * @return Request URI for operation. - * @throws RuntimeException If operation can't be found. + * @return Request URI for operation, or empty if the shape is not a direct operation input. */ - private String findRequestUri(Shape parentShape, Map allC2jShapes) { + private Optional findRequestUri(Shape parentShape, Map allC2jShapes) { Optional operation = builder.getService().getOperations().values().stream() .filter(o -> o.getInput() != null) .filter(o -> allC2jShapes.get(o.getInput().getShape()).equals(parentShape)) .findFirst(); - return operation.map(o -> o.getHttp().getRequestUri()) - .orElseThrow(() -> { - String detailMsg = "Could not find request URI for input shape for operation: " + operation; - ValidationEntry entry = - new ValidationEntry().withErrorId(ValidationErrorId.REQUEST_URI_NOT_FOUND) - .withDetailMessage(detailMsg) - .withSeverity(ValidationErrorSeverity.DANGER); - return ModelInvalidException.builder().validationEntries(Collections.singletonList(entry)).build(); - }); + if (!operation.isPresent()) { + // Not a direct operation input shape, should be ignored. + // https://smithy.io/2.0/spec/http-bindings.html#httplabel-is-only-used-on-top-level-input + return Optional.empty(); + } + + String requestUri = operation.get().getHttp().getRequestUri(); + if (requestUri == null) { + String shapeName = allC2jShapes.entrySet().stream() + .filter(e -> e.getValue().equals(parentShape)) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Shape not found in model: " + parentShape)); + String detailMsg = "Could not find request URI for input shape '" + shapeName + + "'. No operation was found that references this shape as its input."; + ValidationEntry entry = + new ValidationEntry().withErrorId(ValidationErrorId.REQUEST_URI_NOT_FOUND) + .withDetailMessage(detailMsg) + .withSeverity(ValidationErrorSeverity.DANGER); + throw ModelInvalidException.builder().validationEntries(Collections.singletonList(entry)).build(); + } + + return Optional.of(requestUri); } private String deriveUnmarshallerLocationName(Shape memberShape, String memberName, Member member) { diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/AddShapesTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/AddShapesTest.java index dde88dd226e0..45185281666c 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/AddShapesTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/AddShapesTest.java @@ -84,8 +84,23 @@ void generateShapeModel_memberRequiredByNestedShape_setsMemberModelAsRequired() MemberModel requiredMemberModel = requestShapeModel.findMemberModelByC2jName(queryParamName); assertThat(requestShapeModel.getRequired()).contains(queryParamName); - assertThat(requiredMemberModel.getHttp().getLocation()).isEqualTo(Location.QUERY_STRING); + assertThat(requiredMemberModel.getHttp().getLocation()).isNull(); assertThat(requiredMemberModel.isRequired()).isTrue(); } + @Test + void generateShapeModel_locationOnNestedShape_isIgnored() { + ShapeModel nestedShape = intermediateModel.getShapes().get("NestedQueryParameterOperation"); + MemberModel queryParam = nestedShape.findMemberModelByC2jName("QueryParamOne"); + assertThat(queryParam.getHttp().getLocation()).isNull(); + } + + @Test + void generateShapeModel_locationOnDirectInputShape_isPreserved() { + ShapeModel inputShape = intermediateModel.getShapes().get("QueryParameterOperationRequest"); + assertThat(inputShape.findMemberModelByC2jName("PathParam").getHttp().getLocation()).isEqualTo(Location.URI); + assertThat(inputShape.findMemberModelByC2jName("QueryParamOne").getHttp().getLocation()).isEqualTo(Location.QUERY_STRING); + assertThat(inputShape.findMemberModelByC2jName("StringHeaderMember").getHttp().getLocation()).isEqualTo(Location.HEADER); + } + } diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java index 05a492ca24c4..194a315537b8 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/CodeGeneratorTest.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.codegen; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -43,11 +44,15 @@ import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.config.customization.UnderscoresInNameBehavior; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.intermediate.MemberModel; +import software.amazon.awssdk.codegen.model.intermediate.ShapeModel; import software.amazon.awssdk.codegen.model.rules.endpoints.EndpointTestSuiteModel; +import software.amazon.awssdk.codegen.model.service.Location; import software.amazon.awssdk.codegen.model.service.ServiceModel; import software.amazon.awssdk.codegen.poet.ClientTestModels; import software.amazon.awssdk.codegen.validation.ModelInvalidException; import software.amazon.awssdk.codegen.validation.ModelValidator; +import software.amazon.awssdk.codegen.validation.ValidationEntry; import software.amazon.awssdk.codegen.validation.ValidationErrorId; public class CodeGeneratorTest { @@ -176,6 +181,41 @@ void execute_endpointsTestReferencesUnknownOperationMember_throwsValidationError }); } + @Test + void execute_uriLocationOnNonInputShape_isIgnored() throws IOException { + C2jModels models = C2jModels.builder() + .customizationConfig(CustomizationConfig.create()) + .serviceModel(getUriOnNonInputShapeServiceModel()) + .build(); + + // Per the Smithy spec, httpLabel on non-input shapes has no meaning and is simply ignored. + assertThatNoException().isThrownBy( + () -> generateCodeFromC2jModels(models, outputDir, true, Collections.emptyList())); + + IntermediateModel intermediateModel = new IntermediateModelBuilder(models).build(); + + ShapeModel inputShape = intermediateModel.getShapes().get("SomeOperationRequest"); + assertThat(inputShape.findMemberModelByC2jName("thingId").getHttp().getLocation()).isEqualTo(Location.URI); + + ShapeModel nestedShape = intermediateModel.getShapes().get("NestedOptions"); + assertThat(nestedShape.findMemberModelByC2jName("pageSize").getHttp().getLocation()).isNull(); + assertThat(nestedShape.findMemberModelByC2jName("pageSize").getHttp().isGreedy()).isFalse(); + assertThat(nestedShape.findMemberModelByC2jName("headerParam").getHttp().getLocation()).isNull(); + assertThat(nestedShape.findMemberModelByC2jName("queryParam").getHttp().getLocation()).isNull(); + assertThat(nestedShape.findMemberModelByC2jName("prefixHeaders").getHttp().getLocation()).isNull(); + + ShapeModel sharedShape = intermediateModel.getShapes().get("SharedShapeOperationRequest"); + assertThat(sharedShape.findMemberModelByC2jName("sharedId").getHttp().getLocation()).isEqualTo(Location.URI); + + Path generatedNestedOptions = Files.walk(outputDir) + .filter(p -> p.getFileName().toString().equals("NestedOptions.java")) + .findFirst() + .orElseThrow(() -> new AssertionError("NestedOptions.java not found in generated output")); + String actual = new String(Files.readAllBytes(generatedNestedOptions), StandardCharsets.UTF_8); + String expected = resourceAsString("expected-nested-options.java"); + assertThat(actual).isEqualTo(expected); + } + @Test void execute_operationHasNoRequestUri_throwsValidationError() throws IOException { C2jModels models = C2jModels.builder() @@ -244,6 +284,11 @@ private ServiceModel getMissingRequestUriServiceModel() throws IOException { return Jackson.load(ServiceModel.class, json); } + private ServiceModel getUriOnNonInputShapeServiceModel() throws IOException { + String json = resourceAsString("uri-on-non-input-shape-service.json"); + return Jackson.load(ServiceModel.class, json); + } + private String resourceAsString(String name) throws IOException { ByteArrayOutputStream baos; try (InputStream resourceAsStream = getClass().getResourceAsStream(name)) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/expected-nested-options.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/expected-nested-options.java new file mode 100644 index 000000000000..4940bbc56a14 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/expected-nested-options.java @@ -0,0 +1,379 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with + * the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +package software.amazon.awssdk.services.restjson.model; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.Mutable; +import software.amazon.awssdk.annotations.NotThreadSafe; +import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; +import software.amazon.awssdk.core.protocol.MarshallLocation; +import software.amazon.awssdk.core.protocol.MarshallingType; +import software.amazon.awssdk.core.traits.LocationTrait; +import software.amazon.awssdk.core.traits.MapTrait; +import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap; +import software.amazon.awssdk.core.util.SdkAutoConstructMap; +import software.amazon.awssdk.utils.ToString; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + */ +@Generated("software.amazon.awssdk:codegen") +public final class NestedOptions implements SdkPojo, Serializable, ToCopyableBuilder { + private static final SdkField PAGE_SIZE_FIELD = SdkField. builder(MarshallingType.STRING) + .memberName("pageSize").getter(getter(NestedOptions::pageSize)).setter(setter(Builder::pageSize)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("pageSize").build()).build(); + + private static final SdkField HEADER_PARAM_FIELD = SdkField. builder(MarshallingType.STRING) + .memberName("headerParam").getter(getter(NestedOptions::headerParam)).setter(setter(Builder::headerParam)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("x-amz-nested-header").build()) + .build(); + + private static final SdkField QUERY_PARAM_FIELD = SdkField. builder(MarshallingType.STRING) + .memberName("queryParam").getter(getter(NestedOptions::queryParam)).setter(setter(Builder::queryParam)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("nestedQuery").build()).build(); + + private static final SdkField> PREFIX_HEADERS_FIELD = SdkField + .> builder(MarshallingType.MAP) + .memberName("prefixHeaders") + .getter(getter(NestedOptions::prefixHeaders)) + .setter(setter(Builder::prefixHeaders)) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("x-amz-prefix-").build(), + MapTrait.builder() + .keyLocationName("key") + .valueLocationName("value") + .valueFieldInfo( + SdkField. builder(MarshallingType.STRING) + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD) + .locationName("value").build()).build()).build()).build(); + + private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(PAGE_SIZE_FIELD, + HEADER_PARAM_FIELD, QUERY_PARAM_FIELD, PREFIX_HEADERS_FIELD)); + + private static final Map> SDK_NAME_TO_FIELD = memberNameToFieldInitializer(); + + private static final long serialVersionUID = 1L; + + private final String pageSize; + + private final String headerParam; + + private final String queryParam; + + private final Map prefixHeaders; + + private NestedOptions(BuilderImpl builder) { + this.pageSize = builder.pageSize; + this.headerParam = builder.headerParam; + this.queryParam = builder.queryParam; + this.prefixHeaders = builder.prefixHeaders; + } + + /** + * Returns the value of the PageSize property for this object. + * + * @return The value of the PageSize property for this object. + */ + public final String pageSize() { + return pageSize; + } + + /** + * Returns the value of the HeaderParam property for this object. + * + * @return The value of the HeaderParam property for this object. + */ + public final String headerParam() { + return headerParam; + } + + /** + * Returns the value of the QueryParam property for this object. + * + * @return The value of the QueryParam property for this object. + */ + public final String queryParam() { + return queryParam; + } + + /** + * For responses, this returns true if the service returned a value for the PrefixHeaders property. This DOES NOT + * check that the value is non-empty (for which, you should check the {@code isEmpty()} method on the property). + * This is useful because the SDK will never return a null collection or map, but you may need to differentiate + * between the service returning nothing (or null) and the service returning an empty collection or map. For + * requests, this returns true if a value for the property was specified in the request builder, and false if a + * value was not specified. + */ + public final boolean hasPrefixHeaders() { + return prefixHeaders != null && !(prefixHeaders instanceof SdkAutoConstructMap); + } + + /** + * Returns the value of the PrefixHeaders property for this object. + *

+ * Attempts to modify the collection returned by this method will result in an UnsupportedOperationException. + *

+ *

+ * This method will never return null. If you would like to know whether the service returned this field (so that + * you can differentiate between null and empty), you can use the {@link #hasPrefixHeaders} method. + *

+ * + * @return The value of the PrefixHeaders property for this object. + */ + public final Map prefixHeaders() { + return prefixHeaders; + } + + @Override + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public static Class serializableBuilderClass() { + return BuilderImpl.class; + } + + @Override + public final int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(pageSize()); + hashCode = 31 * hashCode + Objects.hashCode(headerParam()); + hashCode = 31 * hashCode + Objects.hashCode(queryParam()); + hashCode = 31 * hashCode + Objects.hashCode(hasPrefixHeaders() ? prefixHeaders() : null); + return hashCode; + } + + @Override + public final boolean equals(Object obj) { + return equalsBySdkFields(obj); + } + + @Override + public final boolean equalsBySdkFields(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof NestedOptions)) { + return false; + } + NestedOptions other = (NestedOptions) obj; + return Objects.equals(pageSize(), other.pageSize()) && Objects.equals(headerParam(), other.headerParam()) + && Objects.equals(queryParam(), other.queryParam()) && hasPrefixHeaders() == other.hasPrefixHeaders() + && Objects.equals(prefixHeaders(), other.prefixHeaders()); + } + + /** + * Returns a string representation of this object. This is useful for testing and debugging. Sensitive data will be + * redacted from this string using a placeholder value. + */ + @Override + public final String toString() { + return ToString.builder("NestedOptions").add("PageSize", pageSize()).add("HeaderParam", headerParam()) + .add("QueryParam", queryParam()).add("PrefixHeaders", hasPrefixHeaders() ? prefixHeaders() : null).build(); + } + + public final Optional getValueForField(String fieldName, Class clazz) { + switch (fieldName) { + case "pageSize": + return Optional.ofNullable(clazz.cast(pageSize())); + case "headerParam": + return Optional.ofNullable(clazz.cast(headerParam())); + case "queryParam": + return Optional.ofNullable(clazz.cast(queryParam())); + case "prefixHeaders": + return Optional.ofNullable(clazz.cast(prefixHeaders())); + default: + return Optional.empty(); + } + } + + @Override + public final List> sdkFields() { + return SDK_FIELDS; + } + + @Override + public final Map> sdkFieldNameToField() { + return SDK_NAME_TO_FIELD; + } + + private static Map> memberNameToFieldInitializer() { + Map> map = new HashMap<>(); + map.put("pageSize", PAGE_SIZE_FIELD); + map.put("x-amz-nested-header", HEADER_PARAM_FIELD); + map.put("nestedQuery", QUERY_PARAM_FIELD); + map.put("x-amz-prefix-", PREFIX_HEADERS_FIELD); + return Collections.unmodifiableMap(map); + } + + private static Function getter(Function g) { + return obj -> g.apply((NestedOptions) obj); + } + + private static BiConsumer setter(BiConsumer s) { + return (obj, val) -> s.accept((Builder) obj, val); + } + + @Mutable + @NotThreadSafe + public interface Builder extends SdkPojo, CopyableBuilder { + /** + * Sets the value of the PageSize property for this object. + * + * @param pageSize + * The new value for the PageSize property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder pageSize(String pageSize); + + /** + * Sets the value of the HeaderParam property for this object. + * + * @param headerParam + * The new value for the HeaderParam property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder headerParam(String headerParam); + + /** + * Sets the value of the QueryParam property for this object. + * + * @param queryParam + * The new value for the QueryParam property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder queryParam(String queryParam); + + /** + * Sets the value of the PrefixHeaders property for this object. + * + * @param prefixHeaders + * The new value for the PrefixHeaders property for this object. + * @return Returns a reference to this object so that method calls can be chained together. + */ + Builder prefixHeaders(Map prefixHeaders); + } + + static final class BuilderImpl implements Builder { + private String pageSize; + + private String headerParam; + + private String queryParam; + + private Map prefixHeaders = DefaultSdkAutoConstructMap.getInstance(); + + private BuilderImpl() { + } + + private BuilderImpl(NestedOptions model) { + pageSize(model.pageSize); + headerParam(model.headerParam); + queryParam(model.queryParam); + prefixHeaders(model.prefixHeaders); + } + + public final String getPageSize() { + return pageSize; + } + + public final void setPageSize(String pageSize) { + this.pageSize = pageSize; + } + + @Override + public final Builder pageSize(String pageSize) { + this.pageSize = pageSize; + return this; + } + + public final String getHeaderParam() { + return headerParam; + } + + public final void setHeaderParam(String headerParam) { + this.headerParam = headerParam; + } + + @Override + public final Builder headerParam(String headerParam) { + this.headerParam = headerParam; + return this; + } + + public final String getQueryParam() { + return queryParam; + } + + public final void setQueryParam(String queryParam) { + this.queryParam = queryParam; + } + + @Override + public final Builder queryParam(String queryParam) { + this.queryParam = queryParam; + return this; + } + + public final Map getPrefixHeaders() { + if (prefixHeaders instanceof SdkAutoConstructMap) { + return null; + } + return prefixHeaders; + } + + public final void setPrefixHeaders(Map prefixHeaders) { + this.prefixHeaders = MapOfStringsCopier.copy(prefixHeaders); + } + + @Override + public final Builder prefixHeaders(Map prefixHeaders) { + this.prefixHeaders = MapOfStringsCopier.copy(prefixHeaders); + return this; + } + + @Override + public NestedOptions build() { + return new NestedOptions(this); + } + + @Override + public List> sdkFields() { + return SDK_FIELDS; + } + + @Override + public Map> sdkFieldNameToField() { + return SDK_NAME_TO_FIELD; + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/nestedqueryparameteroperation.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/nestedqueryparameteroperation.java index c935db9281f0..e743d7c2bea4 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/nestedqueryparameteroperation.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/model/nestedqueryparameteroperation.java @@ -33,13 +33,13 @@ public final class NestedQueryParameterOperation implements SdkPojo, Serializabl .memberName("QueryParamOne") .getter(getter(NestedQueryParameterOperation::queryParamOne)) .setter(setter(Builder::queryParamOne)) - .traits(LocationTrait.builder().location(MarshallLocation.QUERY_PARAM).locationName("QueryParamOne").build(), + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("QueryParamOne").build(), RequiredTrait.create()).build(); private static final SdkField QUERY_PARAM_TWO_FIELD = SdkField. builder(MarshallingType.STRING) .memberName("QueryParamTwo").getter(getter(NestedQueryParameterOperation::queryParamTwo)) .setter(setter(Builder::queryParamTwo)) - .traits(LocationTrait.builder().location(MarshallLocation.QUERY_PARAM).locationName("QueryParamTwo").build()).build(); + .traits(LocationTrait.builder().location(MarshallLocation.PAYLOAD).locationName("QueryParamTwo").build()).build(); private static final List> SDK_FIELDS = Collections.unmodifiableList(Arrays.asList(QUERY_PARAM_ONE_FIELD, QUERY_PARAM_TWO_FIELD)); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/uri-on-non-input-shape-service.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/uri-on-non-input-shape-service.json new file mode 100644 index 000000000000..f858a11b94ed --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/uri-on-non-input-shape-service.json @@ -0,0 +1,100 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2010-05-08", + "endpointPrefix": "json-service-endpoint", + "globalEndpoint": "json-service.amazonaws.com", + "protocol": "rest-json", + "serviceAbbreviation": "Rest Json Service", + "serviceFullName": "Some Service That Uses Rest-Json Protocol", + "serviceId": "Rest Json Service", + "signingName": "json-service", + "signatureVersion": "v4", + "uid": "json-service-2010-05-08", + "xmlNamespace": "https://json-service.amazonaws.com/doc/2010-05-08/" + }, + "operations": { + "SomeOperation": { + "name": "SomeOperation", + "http": { + "method": "POST", + "requestUri": "/things/{thingId}" + }, + "input": { + "shape": "SomeOperationRequest" + } + }, + "SharedShapeOperation": { + "name": "SharedShapeOperation", + "http": { + "method": "GET", + "requestUri": "/shared/{sharedId}" + }, + "input": { + "shape": "SharedShape" + } + } + }, + "shapes": { + "SomeOperationRequest": { + "type": "structure", + "members": { + "thingId": { + "shape": "String", + "location": "uri", + "locationName": "thingId" + }, + "options": { + "shape": "NestedOptions" + }, + "shared": { + "shape": "SharedShape" + } + } + }, + "NestedOptions": { + "type": "structure", + "members": { + "pageSize": { + "shape": "String", + "location": "uri", + "locationName": "pageSize" + }, + "headerParam": { + "shape": "String", + "location": "header", + "locationName": "x-amz-nested-header" + }, + "queryParam": { + "shape": "String", + "location": "querystring", + "locationName": "nestedQuery" + }, + "prefixHeaders": { + "shape": "MapOfStrings", + "location": "headers", + "locationName": "x-amz-prefix-" + } + } + }, + "SharedShape": { + "type": "structure", + "members": { + "sharedId": { + "shape": "String", + "location": "uri", + "locationName": "sharedId" + } + } + }, + "String": { + "type": "string" + }, + "MapOfStrings": { + "type": "map", + "key": {"shape": "String"}, + "value": {"shape": "String"} + } + }, + "documentation": "A service with HTTP binding locations on non-input shapes" +}