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