Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ public void postprocess(IntermediateModel intermediateModel) {
// no-op
}

/**
* Applies the same check to event streams found in a Smithy model.
*/
static void validateEventStreams(String serviceId, Iterable<String> eventStreamNames) {
Set<String> approvedStreams = APPROVED_EVENT_STREAMS.get(serviceId);
if (approvedStreams == null) {
return;
}
for (String name : eventStreamNames) {
if (!approvedStreams.contains(name)) {
throw unknownStreamError(serviceId, name);
}
}
}

private static RuntimeException unknownStreamError(String serviceId, String evenstreamName) {
String msg = String.format("Encountered a new eventstream for service %s: %s. This service contains " +
"evenstreams that are code generated using an older style that requires a customization. Please " +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* 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.codegen.customization.processors;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.codegen.validation.ModelInvalidException;
import software.amazon.awssdk.codegen.validation.ValidationEntry;
import software.amazon.awssdk.codegen.validation.ValidationErrorId;
import software.amazon.awssdk.codegen.validation.ValidationErrorSeverity;
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.neighbor.Walker;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StringShape;
import software.amazon.smithy.model.traits.HttpPayloadTrait;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.model.traits.TraitDefinition;

/**
* The Smithy path's counterpart to the validation-only C2J preprocessors: {@link NewAndLegacyEventStreamProcessor},
* {@link ExplicitStringPayloadQueryProtocolProcessor}, and {@link LowercaseShapeValidatorProcessor}. Checks the shapes
* the service uses, after smithy-build transforms, and fails with the same errors as C2J.
*/
@SdkInternalApi
public final class SmithySourceModelValidator {

private SmithySourceModelValidator() {
}

public static void validate(Model model, ServiceShape service, String protocol) {
Set<Shape> serviceShapes = new Walker(model).walkShapes(service);
validateEventStreams(service, serviceShapes);
validateNoExplicitStringPayload(model, service, protocol);
validateShapeNames(serviceShapes);
}

private static void validateEventStreams(ServiceShape service, Set<Shape> serviceShapes) {
service.getTrait(ServiceTrait.class).ifPresent(serviceTrait -> {
List<String> eventStreams = serviceShapes.stream()
.filter(s -> s.isUnionShape() && s.hasTrait(StreamingTrait.class))
.map(s -> s.getId().getName())
.sorted()
.collect(Collectors.toList());
NewAndLegacyEventStreamProcessor.validateEventStreams(serviceTrait.getSdkId(), eventStreams);
});
}

private static void validateNoExplicitStringPayload(Model model, ServiceShape service, String protocol) {
if (!"ec2".equals(protocol) && !"query".equals(protocol)) {
return;
}
for (ShapeId operationId : service.getAllOperations()) {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
if (hasExplicitStringPayload(model, operation.getInputShape())
|| hasExplicitStringPayload(model, operation.getOutputShape())) {
throw new RuntimeException("Operations with explicit String payloads are not supported for Query "
+ "protocols. Unsupported operation: " + operationId.getName());
}
}
}

private static boolean hasExplicitStringPayload(Model model, ShapeId structureId) {
for (MemberShape member : model.expectShape(structureId).members()) {
if (member.hasTrait(HttpPayloadTrait.class) && model.expectShape(member.getTarget()) instanceof StringShape) {
return true;
}
}
return false;
}

private static void validateShapeNames(Set<Shape> serviceShapes) {
serviceShapes.stream()
.filter(s -> (s.isStructureShape() || s.isUnionShape()) && !s.hasTrait(TraitDefinition.class))
.map(s -> s.getId().getName())
.filter(name -> Character.isLowerCase(name.charAt(0)))
.sorted()
.findFirst()
.ifPresent(name -> {
String errorMsg = String.format("Shape name '%s' starts with a lowercase character. Shape names "
+ "must start with an uppercase character. Please update the "
+ "shape name in your service model", name);
throw ModelInvalidException.fromEntry(
ValidationEntry.create(ValidationErrorId.INVALID_IDENTIFIER_NAME,
ValidationErrorSeverity.DANGER, errorMsg));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,16 @@ private ShapeMarshaller buildMarshaller(OperationShape op, HttpBindingIndex bind
.withAction(op.toShapeId().getName())
.withProtocol(protocol);

// RPC protocols ignore any @http trait and always POST to "/". For smithy-rpc-v2-cbor the
// "/service/{id}/operation/{op}" URI is applied later by the deferred rpcv2Cbor processor.
// RPC protocols ignore any @http trait and always POST, to "/" except for smithy-rpc-v2-cbor,
// which addresses each operation by path. C2J sets that path in SmithyRpcV2CborProtocolProcessor.
if (httpBindingsHonored() && op.hasTrait(HttpTrait.class)) {
HttpTrait http = op.expectTrait(HttpTrait.class);
marshaller.withVerb(http.getMethod());
marshaller.withRequestUri(http.getUri().toString());
} else if (Protocol.SMITHY_RPC_V2_CBOR.getValue().equals(protocol)) {
marshaller.withVerb("POST");
marshaller.withRequestUri(String.format("/service/%s/operation/%s",
getService().getId().getName(), op.toShapeId().getName()));
} else {
marshaller.withVerb("POST");
marshaller.withRequestUri("/");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import software.amazon.awssdk.codegen.model.intermediate.Protocol;
import software.amazon.awssdk.codegen.model.service.AuthType;
import software.amazon.awssdk.codegen.naming.NamingStrategy;
import software.amazon.awssdk.codegen.utils.ProtocolUtils;
import software.amazon.awssdk.utils.Pair;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.smithy.aws.traits.ServiceTrait;
Expand Down Expand Up @@ -66,14 +65,13 @@ static Metadata constructMetadata(Model model,
ServiceShape service,
ServiceIndex serviceIndex,
NamingStrategy namingStrategy,
CustomizationConfig customizationConfig) {
CustomizationConfig customizationConfig,
String protocol) {
Metadata metadata = new Metadata();

String serviceName = namingStrategy.getServiceName();
configurePackageName(metadata, namingStrategy, customizationConfig);

String protocol = ProtocolUtils.resolveProtocol(serviceIndex, service);

metadata.withApiVersion(service.getVersion())
.withAsyncClient(String.format(Constant.ASYNC_CLIENT_CLASS_NAME_PATTERN, serviceName))
.withAsyncInterface(String.format(Constant.ASYNC_CLIENT_INTERFACE_NAME_PATTERN, serviceName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.codegen.IntermediateModelShapeProcessor;
import software.amazon.awssdk.codegen.customization.processors.SmithyIntermediateModelPostprocessor;
import software.amazon.awssdk.codegen.customization.processors.SmithySourceModelValidator;
import software.amazon.awssdk.codegen.internal.Constant;
import software.amazon.awssdk.codegen.internal.TypeUtils;
import software.amazon.awssdk.codegen.internal.Utils;
Expand Down Expand Up @@ -90,7 +91,7 @@ public SmithyIntermediateModelBuilder(SmithyModels models) {
this.namingStrategy = new DefaultSmithyNamingStrategy(model, service, customConfig);
this.typeUtils = new TypeUtils(namingStrategy);
this.serviceIndex = ServiceIndex.of(model);
this.protocol = ProtocolUtils.resolveProtocol(serviceIndex, service);
this.protocol = ProtocolUtils.resolveProtocol(serviceIndex, service, customConfig);
this.endpointRuleSet = AddSmithyEndpoints.endpointRuleSet(service);
this.endpointTestSuiteModel = AddSmithyEndpoints.endpointTests(service);
this.shapeProcessors = createShapeProcessors();
Expand Down Expand Up @@ -139,6 +140,8 @@ private List<IntermediateModelShapeProcessor> createShapeProcessors() {
}

public IntermediateModel build() {
SmithySourceModelValidator.validate(model, service, protocol);

Map<String, OperationModel> operations =
new TreeMap<>(new AddSmithyOperations(model, service, namingStrategy, customConfig, protocol)
.constructOperations());
Expand Down Expand Up @@ -170,7 +173,7 @@ public IntermediateModel build() {
Map<String, ClientContextParam> clientContextParams = clientContextParams();

IntermediateModel fullModel = new IntermediateModel(
AddSmithyMetadata.constructMetadata(model, service, serviceIndex, namingStrategy, customConfig),
AddSmithyMetadata.constructMetadata(model, service, serviceIndex, namingStrategy, customConfig, protocol),
operations, shapes, customConfig, endpointOperation,
Collections.emptyMap(), namingStrategy, Collections.emptyMap(),
endpointRuleSet, endpointTestSuiteModel, clientContextParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig;
import software.amazon.awssdk.codegen.model.config.customization.MetadataConfig;
import software.amazon.awssdk.codegen.model.service.ServiceMetadata;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.shapes.ServiceShape;
Expand Down Expand Up @@ -113,4 +116,23 @@ public static String resolveProtocol(ServiceIndex serviceIndex, ServiceShape ser
"The SDK does not support any of the protocols applied to service " + service.getId()
+ ": " + protocolTraits.keySet());
}

/**
* Resolves the protocol a Smithy service is generated with. A {@code customServiceMetadata.protocol} override in
* the customization config wins over the protocol traits, the same way it replaces the {@code protocols} list on
* the C2J path; Kinesis uses it to select the legacy {@code cbor} protocol, which has no Smithy trait.
*
* @throws IllegalArgumentException if the override, or every applied protocol trait, is unsupported.
*/
public static String resolveProtocol(ServiceIndex serviceIndex, ServiceShape service,
CustomizationConfig customizationConfig) {
MetadataConfig metadataConfig = customizationConfig.getCustomServiceMetadata();
String override = metadataConfig == null ? null : metadataConfig.getProtocol();
if (override == null) {
return resolveProtocol(serviceIndex, service);
}
ServiceMetadata overridden = new ServiceMetadata();
overridden.setProtocols(Collections.singletonList(override));
return resolveProtocol(overridden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ void restXml_bodyRequest_honorsXmlNameOverrideForRootElement() {
@Test
void rpcProtocol_ignoresHttpBindings_postToRootWithBodyMembers() {
// An RPC service may still carry @http/@httpLabel; the bindings must be ignored, so every
// member goes in the body. The rpcv2Cbor URI is applied later by the deferred processor.
// member goes in the body and the request URI is the rpcv2Cbor operation path.
Model model = modelOf(
"use smithy.protocols#rpcv2Cbor\n",
"@rpcv2Cbor\n",
Expand All @@ -153,7 +153,7 @@ void rpcProtocol_ignoresHttpBindings_postToRootWithBodyMembers() {
ShapeModel req = inputs(model, "smithy-rpc-v2-cbor").get("BodyOpRequest");

assertThat(req.getMarshaller().getVerb()).isEqualTo("POST");
assertThat(req.getMarshaller().getRequestUri()).isEqualTo("/");
assertThat(req.getMarshaller().getRequestUri()).isEqualTo("/service/DemoService/operation/BodyOp");

MemberModel id = req.getMembersAsMap().get("Id");
// @httpLabel ignored: no URI location, and the wire name is the member name, not a label.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import software.amazon.awssdk.codegen.model.service.AuthType;
import software.amazon.awssdk.codegen.naming.DefaultSmithyNamingStrategy;
import software.amazon.awssdk.codegen.naming.NamingStrategy;
import software.amazon.awssdk.codegen.utils.ProtocolUtils;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.shapes.ServiceShape;
Expand Down Expand Up @@ -58,8 +59,9 @@ private static Model modelOf(String protocolUse, String serviceTraits, String ex
private static Metadata metadataOf(Model model) {
ServiceShape service = model.getServiceShapes().iterator().next();
NamingStrategy naming = new DefaultSmithyNamingStrategy(model, service, CustomizationConfig.create());
return AddSmithyMetadata.constructMetadata(model, service, ServiceIndex.of(model), naming,
CustomizationConfig.create());
ServiceIndex serviceIndex = ServiceIndex.of(model);
return AddSmithyMetadata.constructMetadata(model, service, serviceIndex, naming, CustomizationConfig.create(),
ProtocolUtils.resolveProtocol(serviceIndex, service));
}

// ---- jsonVersion ------------------------------------------------------
Expand Down
Loading
Loading