16
16
package software .amazon .smithy .aws .typescript .codegen ;
17
17
18
18
import java .util .List ;
19
+ import java .util .Map ;
20
+ import java .util .TreeMap ;
21
+ import software .amazon .smithy .codegen .core .Symbol ;
19
22
import software .amazon .smithy .codegen .core .SymbolProvider ;
20
23
import software .amazon .smithy .model .knowledge .HttpBinding ;
21
24
import software .amazon .smithy .model .knowledge .HttpBinding .Location ;
24
27
import software .amazon .smithy .model .shapes .MemberShape ;
25
28
import software .amazon .smithy .model .shapes .OperationShape ;
26
29
import software .amazon .smithy .model .shapes .Shape ;
30
+ import software .amazon .smithy .model .shapes .ShapeId ;
27
31
import software .amazon .smithy .model .shapes .ShapeIndex ;
28
- import software .amazon .smithy .model .shapes .SimpleShape ;
29
32
import software .amazon .smithy .model .shapes .StructureShape ;
30
33
import software .amazon .smithy .model .shapes .UnionShape ;
31
34
import software .amazon .smithy .model .traits .JsonNameTrait ;
32
35
import software .amazon .smithy .model .traits .TimestampFormatTrait .Format ;
33
36
import software .amazon .smithy .typescript .codegen .TypeScriptWriter ;
34
37
import software .amazon .smithy .typescript .codegen .integration .HttpBindingProtocolGenerator ;
38
+ import software .amazon .smithy .typescript .codegen .integration .ProtocolGenerator ;
35
39
36
40
public class AwsRestJson1_1 extends HttpBindingProtocolGenerator {
37
41
@@ -50,6 +54,23 @@ protected Format getDocumentTimestampFormat() {
50
54
return Format .EPOCH_SECONDS ;
51
55
}
52
56
57
+ @ Override
58
+ public void generateSharedComponents (GenerationContext context ) {
59
+ super .generateSharedComponents (context );
60
+
61
+ TypeScriptWriter writer = context .getWriter ();
62
+
63
+ writer .addImport ("SerdeContext" , "SerdeContext" , "@aws-sdk/types" );
64
+ writer .openBlock ("const parseBody = (streamBody: any, context: SerdeContext): any => {" , "};" ,
65
+ () -> {
66
+ writer .openBlock ("return context.streamCollector(streamBody).then((body: any) => {" , "});" , () -> {
67
+ writer .write ("return JSON.parse(context.utf8Encoder(body));" );
68
+ });
69
+ });
70
+
71
+ writer .write ("" );
72
+ }
73
+
53
74
@ Override
54
75
protected void serializeDocument (
55
76
GenerationContext context ,
@@ -67,8 +88,7 @@ protected void serializeDocument(
67
88
String locationName = binding .getMember ().getTrait (JsonNameTrait .class )
68
89
.map (JsonNameTrait ::getValue )
69
90
.orElseGet (binding ::getLocationName );
70
- writeDocumentStructureMemberSerialization (context , operation ,
71
- memberName , locationName , binding .getMember ());
91
+ writeDocumentStructureMemberSerialization (context , memberName , locationName , binding .getMember ());
72
92
}
73
93
74
94
writer .write ("body = JSON.stringify(bodyParams);" );
@@ -79,19 +99,20 @@ protected void serializeDocumentStructure(GenerationContext context, StructureSh
79
99
TypeScriptWriter writer = context .getWriter ();
80
100
81
101
writer .write ("let bodyParams: any = {};" );
82
- shape .getAllMembers ().forEach ((memberName , memberShape ) -> {
102
+ // Use a TreeMap to sort the members.
103
+ Map <String , MemberShape > members = new TreeMap <>(shape .getAllMembers ());
104
+ members .forEach ((memberName , memberShape ) -> {
83
105
// Use the jsonName trait value if present, otherwise use the member name.
84
106
String locationName = memberShape .getTrait (JsonNameTrait .class )
85
107
.map (JsonNameTrait ::getValue )
86
108
.orElse (memberName );
87
- writeDocumentStructureMemberSerialization (context , shape , memberName , locationName , memberShape );
109
+ writeDocumentStructureMemberSerialization (context , memberName , locationName , memberShape );
88
110
});
89
111
writer .write ("return bodyParams;" );
90
112
}
91
113
92
114
private void writeDocumentStructureMemberSerialization (
93
115
GenerationContext context ,
94
- Shape container ,
95
116
String memberName ,
96
117
String locationName ,
97
118
MemberShape member
@@ -102,7 +123,8 @@ private void writeDocumentStructureMemberSerialization(
102
123
// Generate an if statement to set the bodyParam if the member is set.
103
124
writer .openBlock ("if (input.$L !== undefined) {" , "}" , memberName , () -> {
104
125
writer .write ("bodyParams['$L'] = $L;" , locationName ,
105
- getInputValue (context , Location .DOCUMENT , container , member , target ));
126
+ // Dispatch to the input value provider for any additional handling.
127
+ getInputValue (context , Location .DOCUMENT , "input." + memberName , member , target ));
106
128
});
107
129
}
108
130
@@ -111,10 +133,9 @@ protected void serializeDocumentCollection(GenerationContext context, Collection
111
133
TypeScriptWriter writer = context .getWriter ();
112
134
Shape target = context .getModel ().getShapeIndex ().getShape (shape .getMember ().getTarget ()).get ();
113
135
114
- // Validate we have input, then get the right serialization for the member target.
115
- writer .write ("input &&" );
116
- writer .openBlock ("input.map(entry =>" , ");" , () -> {
117
- writer .write (getInputValue (context , Location .DOCUMENT , shape , shape .getMember (), target ));
136
+ // Dispatch to the input value provider for any additional handling.
137
+ writer .openBlock ("return (input || []).map(entry =>" , ");" , () -> {
138
+ writer .write (getInputValue (context , Location .DOCUMENT , "entry" , shape .getMember (), target ));
118
139
});
119
140
}
120
141
@@ -123,49 +144,197 @@ protected void serializeDocumentMap(GenerationContext context, MapShape shape) {
123
144
TypeScriptWriter writer = context .getWriter ();
124
145
Shape target = context .getModel ().getShapeIndex ().getShape (shape .getValue ().getTarget ()).get ();
125
146
126
- // Validate we have input, then get the right serialization for the map value.
127
- writer .write ("input.name &&" );
128
- writer .openBlock ("input.value && {" , "};" , () -> {
129
- writer .write ("name: input.name," );
130
- writer .write ("value: $L" , getInputValue (context , Location .DOCUMENT , shape , shape .getValue (), target ));
147
+ // Get the right serialization for each entry in the map. Undefined
148
+ // inputs won't have this serializer invoked.
149
+ writer .write ("let mapParams: any = {};" );
150
+ writer .openBlock ("Object.keys(input).forEach(key => {" , "});" , () -> {
151
+ writer .write ("mapParams[key] = $L;" ,
152
+ // Dispatch to the input value provider for any additional handling.
153
+ getInputValue (context , Location .DOCUMENT , "input[key]" , shape .getValue (), target ));
131
154
});
155
+ writer .write ("return mapParams;" );
132
156
}
133
157
134
- // TODO Collection cleanup point
135
- // This, and the location it is invoked, can be cleaned up if/when a
136
- // centralized way to check for and/or handle these differences is built.
137
- private boolean isSimpleCollection (ShapeIndex index , Shape shape ) {
138
- if (shape instanceof CollectionShape ) {
139
- Shape target = index .getShape (((CollectionShape ) shape ).getMember ().getTarget ()).get ();
140
- if (target instanceof CollectionShape ) {
141
- return isSimpleCollection (index , target );
142
- }
143
- return target instanceof SimpleShape ;
158
+ @ Override
159
+ protected void serializeDocumentUnion (GenerationContext context , UnionShape shape ) {
160
+ TypeScriptWriter writer = context .getWriter ();
161
+ ShapeIndex index = context .getModel ().getShapeIndex ();
162
+
163
+ // Visit over the union type, then get the right serialization for the member.
164
+ writer .openBlock ("return $L.visit(input, {" , "});" , shape .getId ().getName (), () -> {
165
+ // Use a TreeMap to sort the members.
166
+ Map <String , MemberShape > members = new TreeMap <>(shape .getAllMembers ());
167
+ members .forEach ((memberName , memberShape ) -> {
168
+ Shape target = index .getShape (memberShape .getTarget ()).get ();
169
+ // Dispatch to the input value provider for any additional handling.
170
+ writer .write ("$L: value => $L," , memberName ,
171
+ getInputValue (context , Location .DOCUMENT , "value" , memberShape , target ));
172
+ });
173
+ writer .write ("_: value => value" );
174
+ });
175
+ }
176
+
177
+ @ Override
178
+ protected void deserializeDocument (
179
+ GenerationContext context ,
180
+ OperationShape operation ,
181
+ List <HttpBinding > documentBindings
182
+ ) {
183
+ TypeScriptWriter writer = context .getWriter ();
184
+ SymbolProvider symbolProvider = context .getSymbolProvider ();
185
+
186
+ for (HttpBinding binding : documentBindings ) {
187
+ Shape target = context .getModel ().getShapeIndex ().getShape (binding .getMember ().getTarget ()).get ();
188
+ // The name of the member to get from the input shape.
189
+ String memberName = symbolProvider .toMemberName (binding .getMember ());
190
+ // Use the jsonName trait value if present, otherwise use the member name.
191
+ String locationName = binding .getMember ().getTrait (JsonNameTrait .class )
192
+ .map (JsonNameTrait ::getValue )
193
+ .orElseGet (binding ::getLocationName );
194
+ writer .openBlock ("if (data.$L !== undefined) {" , "}" , locationName , () -> {
195
+ writer .write ("contents.$L = $L;" , memberName ,
196
+ // Dispatch to the output value provider for any additional handling.
197
+ getOutputValue (context , Location .DOCUMENT , "data." + locationName ,
198
+ binding .getMember (), target ));
199
+ });
144
200
}
145
- return false ;
146
201
}
147
202
148
203
@ Override
149
- protected void serializeDocumentUnion (GenerationContext context , UnionShape shape ) {
204
+ protected void writeErrorDeserializationDispatcher (
205
+ GenerationContext context ,
206
+ List <ShapeId > errors ,
207
+ String unknownErrorNamespace
208
+ ) {
209
+ TypeScriptWriter writer = context .getWriter ();
150
210
SymbolProvider symbolProvider = context .getSymbolProvider ();
211
+
212
+ writer .write ("let errorCode: String = output.headers[\" x-amzn-errortype\" ].split(':')[0];" );
213
+ writer .openBlock ("switch (errorCode) {" , "}" , () -> {
214
+ // Generate the case statement for each error, invoking the specific deserializer.
215
+ errors .forEach (errorId -> {
216
+ Shape error = context .getModel ().getShapeIndex ().getShape (errorId ).get ();
217
+ Symbol symbol = symbolProvider .toSymbol (error );
218
+ String methodName = ProtocolGenerator .getDeserFunctionName (symbol , getName ());
219
+ writer .openBlock ("case $S:\n case $S:" , " break;" , errorId .getName (), errorId .toString (), () -> {
220
+ // Dispatch to the error deserialization function.
221
+ writer .write ("response = $L(data, context);" , methodName );
222
+ });
223
+ });
224
+
225
+ // Build a generic error the best we can for ones we don't know about.
226
+ writer .openBlock ("default:" , "" , () -> {
227
+ writer .write ("errorCode = errorCode || \" UnknownError\" ;" );
228
+ writer .openBlock ("response = {" , "};" , () -> {
229
+ writer .write ("__type: `$L#$${errorCode}`," , unknownErrorNamespace );
230
+ writer .write ("$$name: errorCode," );
231
+ writer .write ("$$fault: \" client\" ," );
232
+ });
233
+ });
234
+ });
235
+ }
236
+
237
+ @ Override
238
+ protected void deserializeError (GenerationContext context , StructureShape shape ) {
239
+ // Use a TreeMap to sort the members.
240
+ Map <String , MemberShape > members = new TreeMap <>(shape .getAllMembers ());
241
+ members .forEach ((memberName , memberShape ) -> {
242
+ // Use the jsonName trait value if present, otherwise use the member name.
243
+ String locationName = memberShape .getTrait (JsonNameTrait .class )
244
+ .map (JsonNameTrait ::getValue )
245
+ .orElse (memberName );
246
+ writeDocumentStructureMemberDeserialization (context , memberName , locationName , memberShape );
247
+ });
248
+ }
249
+
250
+ @ Override
251
+ protected void deserializeDocumentStructure (GenerationContext context , StructureShape shape ) {
252
+ TypeScriptWriter writer = context .getWriter ();
253
+
254
+ // Prepare the document contents structure.
255
+ writer .openBlock ("let contents: any = {" , "};" , () -> {
256
+ writer .write ("$$namespace: $S," , shape .getId ().getNamespace ());
257
+ writer .write ("$$name: $S" , shape .getId ().getName ());
258
+ });
259
+ // Use a TreeMap to sort the members.
260
+ Map <String , MemberShape > members = new TreeMap <>(shape .getAllMembers ());
261
+ members .forEach ((memberName , memberShape ) -> {
262
+ // Use the jsonName trait value if present, otherwise use the member name.
263
+ String locationName = memberShape .getTrait (JsonNameTrait .class )
264
+ .map (JsonNameTrait ::getValue )
265
+ .orElse (memberName );
266
+ writeDocumentStructureMemberDeserialization (context , memberName , locationName , memberShape );
267
+ });
268
+
269
+ writer .write ("return contents;" );
270
+ }
271
+
272
+ private void writeDocumentStructureMemberDeserialization (
273
+ GenerationContext context ,
274
+ String memberName ,
275
+ String locationName ,
276
+ MemberShape member
277
+ ) {
278
+ TypeScriptWriter writer = context .getWriter ();
279
+ Shape target = context .getModel ().getShapeIndex ().getShape (member .getTarget ()).get ();
280
+
281
+ // Generate an if statement to set the bodyParam if the member is set.
282
+ writer .openBlock ("if (output.$L !== undefined) {" , "}" , locationName , () -> {
283
+ writer .write ("contents.$L = $L;" , memberName ,
284
+ // Dispatch to the output value provider for any additional handling.
285
+ getOutputValue (context , Location .DOCUMENT , "output." + locationName , member , target ));
286
+ });
287
+ }
288
+
289
+ @ Override
290
+ protected void deserializeDocumentCollection (GenerationContext context , CollectionShape shape ) {
291
+ TypeScriptWriter writer = context .getWriter ();
292
+ Shape target = context .getModel ().getShapeIndex ().getShape (shape .getMember ().getTarget ()).get ();
293
+
294
+ // Dispatch to the output value provider for any additional handling.
295
+ writer .openBlock ("return (output || []).map((entry: any) =>" , ");" , () -> {
296
+ writer .write (getOutputValue (context , Location .DOCUMENT , "entry" , shape .getMember (), target ));
297
+ });
298
+ }
299
+
300
+ @ Override
301
+ protected void deserializeDocumentMap (GenerationContext context , MapShape shape ) {
302
+ TypeScriptWriter writer = context .getWriter ();
303
+ Shape target = context .getModel ().getShapeIndex ().getShape (shape .getValue ().getTarget ()).get ();
304
+
305
+ // Get the right serialization for each entry in the map. Undefined
306
+ // outputs won't have this deserializer invoked.
307
+ writer .write ("let mapParams: any = {};" );
308
+ writer .openBlock ("Object.keys(output).forEach(key => {" , "});" , () -> {
309
+ writer .write ("mapParams[key] = $L;" ,
310
+ // Dispatch to the output value provider for any additional handling.
311
+ getOutputValue (context , Location .DOCUMENT , "output[key]" , shape .getValue (), target ));
312
+ });
313
+ writer .write ("return mapParams;" );
314
+ }
315
+
316
+ @ Override
317
+ protected void deserializeDocumentUnion (GenerationContext context , UnionShape shape ) {
151
318
TypeScriptWriter writer = context .getWriter ();
152
319
ShapeIndex index = context .getModel ().getShapeIndex ();
153
320
154
- // Visit over the union type, then get the right serialization for the member.
155
- writer .openBlock ("$L.visit(input, {" , "});" , shape .getId ().getName (), () -> {
156
- shape .getAllMembers ().forEach ((name , member ) -> {
157
- writer .openBlock ("$L: value => {" , "}," , symbolProvider .toMemberName (member ), () -> {
158
- Shape target = index .getShape (member .getTarget ()).get ();
159
- // TODO See collection cleanup note
160
- // Make sure we invoke the other serialization of union members that need it.
161
- if (!(target instanceof SimpleShape ) && !isSimpleCollection (index , target )) {
162
- writer .write ("$L;" , getInputValue (context , Location .DOCUMENT , shape , member , target ));
163
- } else {
164
- writer .write ("value;" );
165
- }
166
- });
321
+ // Check for any known union members and return when we find one.
322
+ Map <String , MemberShape > members = new TreeMap <>(shape .getAllMembers ());
323
+ members .forEach ((memberName , memberShape ) -> {
324
+ Shape target = index .getShape (memberShape .getTarget ()).get ();
325
+ // Use the jsonName trait value if present, otherwise use the member name.
326
+ String locationName = memberShape .getTrait (JsonNameTrait .class )
327
+ .map (JsonNameTrait ::getValue )
328
+ .orElse (memberName );
329
+ writer .openBlock ("if (output.$L !== undefined) {" , "}" , locationName , () -> {
330
+ writer .openBlock ("return {" , "};" , () -> {
331
+ // Dispatch to the output value provider for any additional handling.
332
+ writer .write ("$L: $L" , memberName , getOutputValue (context , Location .DOCUMENT ,
333
+ "output." + locationName , memberShape , target ));
334
+ });
167
335
});
168
- writer .openBlock ("_: value => {" , "}" , () -> writer .write ("value;" ));
169
336
});
337
+ // Or write to the unknown member the element in the output.
338
+ writer .write ("return { $$unknown: output[Object.keys(output)[0]] };" );
170
339
}
171
340
}
0 commit comments