15
15
16
16
package software .amazon .smithy .aws .typescript .codegen ;
17
17
18
+ import java .util .ArrayList ;
19
+ import java .util .List ;
18
20
import java .util .Map ;
19
- import java .util .function .Supplier ;
21
+ import java .util .function .BiConsumer ;
22
+ import java .util .stream .Collectors ;
20
23
import software .amazon .smithy .codegen .core .CodegenException ;
21
24
import software .amazon .smithy .model .Model ;
22
25
import software .amazon .smithy .model .shapes .CollectionShape ;
@@ -99,17 +102,35 @@ protected void deserializeStructure(GenerationContext context, StructureShape sh
99
102
members .forEach ((memberName , memberShape ) -> writer .write ("$L: undefined," , memberName ));
100
103
});
101
104
102
- members .forEach ((memberName , memberShape ) ->
103
- deserializeNamedStructureMember (context , memberName , memberShape , () -> "output" ));
105
+ members .forEach ((memberName , memberShape ) -> {
106
+ // Grab the target shape so we can use a member deserializer on it.
107
+ Shape target = context .getModel ().expectShape (memberShape .getTarget ());
108
+ deserializeNamedMember (context , memberName , memberShape , "output" , (dataSource , visitor ) -> {
109
+ writer .write ("contents.$L = $L;" , memberName , target .accept (visitor ));
110
+ });
111
+ });
104
112
105
113
writer .write ("return contents;" );
106
114
}
107
115
108
- void deserializeNamedStructureMember (
116
+ /**
117
+ * Generates an if statement for deserializing an output member, validating its
118
+ * presence at the correct location, handling collections and flattening, and
119
+ * dispatching to the supplied function to generate the body of the if statement.
120
+ *
121
+ * @param context The generation context.
122
+ * @param memberName The name of the member being deserialized.
123
+ * @param memberShape The shape of the member being deserialized.
124
+ * @param inputLocation The parent input location of the member being deserialized.
125
+ * @param statementBodyGenerator A function that generates the proper deserialization
126
+ * after member presence is validated.
127
+ */
128
+ void deserializeNamedMember (
109
129
GenerationContext context ,
110
130
String memberName ,
111
131
MemberShape memberShape ,
112
- Supplier <String > inputLocation
132
+ String inputLocation ,
133
+ BiConsumer <String , DocumentMemberDeserVisitor > statementBodyGenerator
113
134
) {
114
135
TypeScriptWriter writer = context .getWriter ();
115
136
Model model = context .getModel ();
@@ -122,28 +143,41 @@ void deserializeNamedStructureMember(
122
143
Shape target = context .getModel ().expectShape (memberShape .getTarget ());
123
144
// Handle @xmlFlattened for collections and maps.
124
145
boolean isFlattened = memberShape .hasTrait (XmlFlattenedTrait .class );
146
+ // Handle targets that return multiple entities per member.
147
+ boolean deserializationReturnsArray = deserializationReturnsArray (target );
125
148
126
149
// Build a string based on the traits that represents the location.
127
150
// Note we don't need to handle @xmlAttribute here because the parser flattens
128
151
// attributes in to the main structure.
129
- StringBuilder sourceBuilder = new StringBuilder (inputLocation .get ())
130
- .append ("['" ).append (locationName ).append ("']" );
131
-
132
- // Go in to a specialized element for unflattened aggregates
133
- if (deserializationReturnsArray (target ) && !isFlattened ) {
152
+ StringBuilder sourceBuilder = new StringBuilder (inputLocation ).append ("['" ).append (locationName ).append ("']" );
153
+ // Track any locations we need to validate on our way to the final element.
154
+ List <String > locationsToValidate = new ArrayList <>();
155
+
156
+ // Go in to a specialized element for unflattened aggregates.
157
+ if (deserializationReturnsArray && !isFlattened ) {
158
+ // Make sure we validate the wrapping element is set.
159
+ locationsToValidate .add (sourceBuilder .toString ());
160
+ // Update the target element.
134
161
String targetLocation = getUnnamedAggregateTargetLocation (model , target );
135
162
sourceBuilder .append ("['" ).append (targetLocation ).append ("']" );
136
163
}
137
164
138
165
// Handle the response property.
139
166
String source = sourceBuilder .toString ();
140
- writer .openBlock ("if ($L !== undefined) {" , "}" , source , () -> {
141
- if (isFlattened ) {
167
+ // Validate the resulting target element is set.
168
+ locationsToValidate .add (source );
169
+ // Build a string of the elements to validate before deserializing.
170
+ String validationStatement = locationsToValidate .stream ()
171
+ .map (location -> location + " !== undefined" )
172
+ .collect (Collectors .joining (" && " ));
173
+ writer .openBlock ("if ($L) {" , "}" , validationStatement , () -> {
174
+ String dataSource = source ;
175
+ // The XML parser will set one K:V for a member that could return multiple entries but only has one.
176
+ if (deserializationReturnsArray ) {
142
177
writer .write ("const wrappedItem = ($1L instanceof Array) ? $1L : [$1L];" , source );
178
+ dataSource = "wrappedItem" ;
143
179
}
144
- writer .write ("contents.$L = $L;" , memberName ,
145
- // Dispatch to the output value provider for any additional handling.
146
- target .accept (getMemberVisitor (isFlattened ? "wrappedItem" : source )));
180
+ statementBodyGenerator .accept (dataSource , getMemberVisitor (dataSource ));
147
181
});
148
182
}
149
183
@@ -165,38 +199,16 @@ private String getUnnamedAggregateTargetLocation(Model model, Shape shape) {
165
199
@ Override
166
200
protected void deserializeUnion (GenerationContext context , UnionShape shape ) {
167
201
TypeScriptWriter writer = context .getWriter ();
168
- Model model = context .getModel ();
169
202
170
203
// Check for any known union members and return when we find one.
171
204
Map <String , MemberShape > members = shape .getAllMembers ();
172
205
members .forEach ((memberName , memberShape ) -> {
173
- // Use the @xmlName trait if present on the member, otherwise use the member name.
174
- String locationName = memberShape .getTrait (XmlNameTrait .class )
175
- .map (XmlNameTrait ::getValue )
176
- .orElse (memberName );
177
206
// Grab the target shape so we can use a member deserializer on it.
178
- Shape target = context .getModel ()
179
- .expectShape (memberShape .getTarget ());
180
- // Handle @xmlFlattened for collections and maps.
181
- boolean isFlattened = memberShape .hasTrait (XmlFlattenedTrait .class );
182
-
183
- // Build a string based on the traits that represents the location.
184
- // Note we don't need to handle @xmlAttribute here because the parser flattens
185
- // attributes in to the main structure.
186
- StringBuilder sourceBuilder = new StringBuilder ("output['" ).append (locationName ).append ("']" );
187
-
188
- // Go in to a specialized element for unflattened aggregates
189
- if (deserializationReturnsArray (target ) && !isFlattened ) {
190
- String targetLocation = getUnnamedAggregateTargetLocation (model , target );
191
- sourceBuilder .append ("['" ).append (targetLocation ).append ("']" );
192
- }
193
-
194
- // Handle the response property.
195
- String source = sourceBuilder .toString ();
196
- writer .openBlock ("if ($L !== undefined) {" , "}" , source , () -> {
207
+ Shape target = context .getModel ().expectShape (memberShape .getTarget ());
208
+ deserializeNamedMember (context , memberName , memberShape , "output" , (dataSource , visitor ) -> {
197
209
writer .openBlock ("return {" , "};" , () -> {
198
210
// Dispatch to the output value provider for any additional handling.
199
- writer .write ("$L: $L" , memberName , target .accept (getMemberVisitor ( source ) ));
211
+ writer .write ("$L: $L" , memberName , target .accept (visitor ));
200
212
});
201
213
});
202
214
});
0 commit comments