1 /* 2 * Copyright 2020 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.smithy.go.codegen.integration; 17 18 import java.util.Collection; 19 import java.util.List; 20 import java.util.stream.Collectors; 21 import software.amazon.smithy.codegen.core.CodegenException; 22 import software.amazon.smithy.codegen.core.SymbolProvider; 23 import software.amazon.smithy.go.codegen.ApplicationProtocol; 24 import software.amazon.smithy.go.codegen.GoDelegator; 25 import software.amazon.smithy.go.codegen.GoSettings; 26 import software.amazon.smithy.go.codegen.GoWriter; 27 import software.amazon.smithy.go.codegen.SyntheticClone; 28 import software.amazon.smithy.model.Model; 29 import software.amazon.smithy.model.shapes.OperationShape; 30 import software.amazon.smithy.model.shapes.ServiceShape; 31 import software.amazon.smithy.model.shapes.Shape; 32 import software.amazon.smithy.model.shapes.ShapeId; 33 import software.amazon.smithy.model.shapes.StructureShape; 34 import software.amazon.smithy.model.traits.Trait; 35 import software.amazon.smithy.utils.CaseUtils; 36 import software.amazon.smithy.utils.StringUtils; 37 38 /** 39 * Smithy protocol code generators. 40 */ 41 public interface ProtocolGenerator { 42 /** 43 * Sanitizes the name of the protocol so it can be used as a symbol 44 * in Go. 45 * 46 * <p>For example, the default implementation converts "." to "_", 47 * and converts "-" to become camelCase separated words. This means 48 * that "aws.rest-json-1.1" becomes "Aws_RestJson1_1". 49 * 50 * @param name Name of the protocol to sanitize. 51 * @return Returns the sanitized name. 52 */ getSanitizedName(String name)53 static String getSanitizedName(String name) { 54 name = name.replaceAll("(\\s|\\.|-)+", "_"); 55 return CaseUtils.toCamelCase(name, true, '_'); 56 } 57 58 /** 59 * Gets the supported protocol {@link ShapeId}. 60 * 61 * @return Returns the protocol supported 62 */ getProtocol()63 ShapeId getProtocol(); 64 getProtocolName()65 default String getProtocolName() { 66 ShapeId protocol = getProtocol(); 67 String prefix = protocol.getNamespace(); 68 int idx = prefix.indexOf('.'); 69 if (idx != -1) { 70 prefix = prefix.substring(0, idx); 71 } 72 return CaseUtils.toCamelCase(prefix) + getSanitizedName(protocol.getName()); 73 } 74 75 /** 76 * Creates an application protocol for the generator. 77 * 78 * @return Returns the created application protocol. 79 */ getApplicationProtocol()80 ApplicationProtocol getApplicationProtocol(); 81 82 /** 83 * Determines if two protocol generators are compatible at the 84 * application protocol level, meaning they both use HTTP, or MQTT 85 * for example. 86 * 87 * <p>Two protocol implementations are considered compatible if the 88 * {@link ApplicationProtocol#equals} method of {@link #getApplicationProtocol} 89 * returns true when called with {@code other}. The default implementation 90 * should work for most interfaces, but may be overridden for more in-depth 91 * handling of things like minor version incompatibilities. 92 * 93 * <p>By default, if the application protocols are considered equal, then 94 * {@code other} is returned. 95 * 96 * @param service Service being generated. 97 * @param protocolGenerators Other protocol generators that are being generated. 98 * @param other Protocol generator to resolve against. 99 * @return Returns the resolved application protocol object. 100 */ resolveApplicationProtocol( ServiceShape service, Collection<ProtocolGenerator> protocolGenerators, ApplicationProtocol other )101 default ApplicationProtocol resolveApplicationProtocol( 102 ServiceShape service, 103 Collection<ProtocolGenerator> protocolGenerators, 104 ApplicationProtocol other 105 ) { 106 if (!getApplicationProtocol().equals(other)) { 107 String protocolNames = protocolGenerators.stream() 108 .map(ProtocolGenerator::getProtocol) 109 .map(Trait::getIdiomaticTraitName) 110 .sorted() 111 .collect(Collectors.joining(", ")); 112 throw new CodegenException(String.format( 113 "All of the protocols generated for a service must be runtime compatible, but " 114 + "protocol `%s` is incompatible with other application protocols: [%s]. Please pick a " 115 + "set of compatible protocols using the `protocols` option when generating %s.", 116 getProtocol(), protocolNames, service.getId())); 117 } 118 119 return other; 120 } 121 122 /** 123 * Generates any standard code for service request/response serde. 124 * 125 * @param context Serde context. 126 */ generateSharedSerializerComponents(GenerationContext context)127 default void generateSharedSerializerComponents(GenerationContext context) { 128 } 129 130 /** 131 * Generates the code used to serialize the shapes of a service 132 * for requests. 133 * 134 * @param context Serialization context. 135 */ generateRequestSerializers(GenerationContext context)136 void generateRequestSerializers(GenerationContext context); 137 138 /** 139 * Generates any standard code for service response deserialization. 140 * 141 * @param context Serde context. 142 */ generateSharedDeserializerComponents(GenerationContext context)143 default void generateSharedDeserializerComponents(GenerationContext context) { 144 } 145 146 /** 147 * Generates the code used to deserialize the shapes of a service 148 * for responses. 149 * 150 * @param context Deserialization context. 151 */ generateResponseDeserializers(GenerationContext context)152 void generateResponseDeserializers(GenerationContext context); 153 154 /** 155 * Generates the code for validating the generated protocol's serializers and deserializers. 156 * 157 * @param context Generation context 158 */ generateProtocolTests(GenerationContext context)159 default void generateProtocolTests(GenerationContext context) { 160 } 161 162 /** 163 * Generates the name of a serializer function for shapes of a service. 164 * 165 * @param shape The shape the serializer function is being generated for. 166 * @param protocol Name of the protocol being generated. 167 * @return Returns the generated function name. 168 */ getOperationHttpBindingsSerFunctionName(Shape shape, String protocol)169 static String getOperationHttpBindingsSerFunctionName(Shape shape, String protocol) { 170 return protocol 171 + "_serializeOpHttpBindings" 172 + StringUtils.capitalize(shape.getId().getName()); 173 } 174 175 /** 176 * Generates the name of a deserializer function for shapes of a service. 177 * 178 * @param shape The shape the deserializer function is being generated for. 179 * @param protocol Name of the protocol being generated. 180 * @return Returns the generated function name. 181 */ getOperationHttpBindingsDeserFunctionName(Shape shape, String protocol)182 static String getOperationHttpBindingsDeserFunctionName(Shape shape, String protocol) { 183 return protocol 184 + "_deserializeOpHttpBindings" 185 + StringUtils.capitalize(shape.getId().getName()); 186 } 187 188 /** 189 * Generates the name of a serializer function for shapes of a service. 190 * 191 * @param shape The shape the serializer function is being generated for. 192 * @param protocol Name of the protocol being generated. 193 * @return Returns the generated function name. 194 */ getDocumentSerializerFunctionName(Shape shape, String protocol)195 static String getDocumentSerializerFunctionName(Shape shape, String protocol) { 196 String extra = ""; 197 if (shape.hasTrait(SyntheticClone.class)) { 198 extra = "Op"; 199 } 200 return protocol + "_serialize" + extra + "Document" + StringUtils.capitalize(shape.getId().getName()); 201 } 202 203 /** 204 * Generates the name of a deserializer function for shapes of a service. 205 * 206 * @param shape The shape the deserializer function is being generated for. 207 * @param protocol Name of the protocol being generated. 208 * @return Returns the generated function name. 209 */ getDocumentDeserializerFunctionName(Shape shape, String protocol)210 static String getDocumentDeserializerFunctionName(Shape shape, String protocol) { 211 String extra = ""; 212 if (shape.hasTrait(SyntheticClone.class)) { 213 extra = "Op"; 214 } 215 return protocol + "_deserialize" + extra + "Document" + StringUtils.capitalize(shape.getId().getName()); 216 } 217 218 getOperationErrorDeserFunctionName(OperationShape shape, String protocol)219 static String getOperationErrorDeserFunctionName(OperationShape shape, String protocol) { 220 return protocol + "_deserializeOpError" + StringUtils.capitalize(shape.getId().getName()); 221 } 222 getErrorDeserFunctionName(StructureShape shape, String protocol)223 static String getErrorDeserFunctionName(StructureShape shape, String protocol) { 224 return protocol + "_deserializeError" + StringUtils.capitalize(shape.getId().getName()); 225 } 226 getSerializeMiddlewareName(ShapeId operationShapeId, String protocol)227 static String getSerializeMiddlewareName(ShapeId operationShapeId, String protocol) { 228 return protocol 229 + "_serializeOp" 230 + operationShapeId.getName(); 231 } 232 getDeserializeMiddlewareName(ShapeId operationShapeId, String protocol)233 static String getDeserializeMiddlewareName(ShapeId operationShapeId, String protocol) { 234 return protocol 235 + "_deserializeOp" 236 + operationShapeId.getName(); 237 } 238 239 /** 240 * Context object used for service serialization and deserialization. 241 */ 242 class GenerationContext { 243 private GoSettings settings; 244 private Model model; 245 private ServiceShape service; 246 private SymbolProvider symbolProvider; 247 private GoWriter writer; 248 private List<GoIntegration> integrations; 249 private String protocolName; 250 private GoDelegator delegator; 251 getSettings()252 public GoSettings getSettings() { 253 return settings; 254 } 255 setSettings(GoSettings settings)256 public void setSettings(GoSettings settings) { 257 this.settings = settings; 258 } 259 getModel()260 public Model getModel() { 261 return model; 262 } 263 setModel(Model model)264 public void setModel(Model model) { 265 this.model = model; 266 } 267 getService()268 public ServiceShape getService() { 269 return service; 270 } 271 setService(ServiceShape service)272 public void setService(ServiceShape service) { 273 this.service = service; 274 } 275 getSymbolProvider()276 public SymbolProvider getSymbolProvider() { 277 return symbolProvider; 278 } 279 setSymbolProvider(SymbolProvider symbolProvider)280 public void setSymbolProvider(SymbolProvider symbolProvider) { 281 this.symbolProvider = symbolProvider; 282 } 283 getWriter()284 public GoWriter getWriter() { 285 return writer; 286 } 287 setWriter(GoWriter writer)288 public void setWriter(GoWriter writer) { 289 this.writer = writer; 290 } 291 getDelegator()292 public GoDelegator getDelegator() { 293 return delegator; 294 } 295 setDelegator(GoDelegator delegator)296 public void setDelegator(GoDelegator delegator) { 297 this.delegator = delegator; 298 } 299 getIntegrations()300 public List<GoIntegration> getIntegrations() { 301 return integrations; 302 } 303 setIntegrations(List<GoIntegration> integrations)304 public void setIntegrations(List<GoIntegration> integrations) { 305 this.integrations = integrations; 306 } 307 getProtocolName()308 public String getProtocolName() { 309 return protocolName; 310 } 311 312 // TODO change to shape id of protocol shape id setProtocolName(String protocolName)313 public void setProtocolName(String protocolName) { 314 this.protocolName = protocolName; 315 } 316 } 317 } 318