@@ -128,24 +128,29 @@ class ParseContext {
128
128
* Initializes a ParseContext with the given source and path.
129
129
*
130
130
* @param dataSource Indicates what kind of API method this data came from.
131
+ * @param methodName The name of the method the user called to create this
132
+ * ParseContext.
131
133
* @param path A path within the object being parsed. This could be an empty
132
- * path (in which case the context represents the root of the data being
133
- * parsed), or a nonempty path (indicating the context represents a nested
134
- * location within the data).
134
+ * path (in which case the context represents the root of the data being
135
+ * parsed), or a nonempty path (indicating the context represents a nested
136
+ * location within the data).
137
+ * @param arrayElement Whether or not this context corresponds to an element
138
+ * of an array.
139
+ * @param fieldTransforms A mutable list of field transforms encountered while
140
+ * parsing the data.
141
+ * @param fieldMask A mutable list of field paths encountered while parsing
142
+ * the data.
135
143
*
136
144
* TODO(b/34871131): We don't support array paths right now, so path can be
137
145
* null to indicate the context represents any location within an array (in
138
146
* which case certain features will not work and errors will be somewhat
139
147
* compromised).
140
- * @param fieldTransforms A mutable list of field transforms encountered while
141
- * parsing the data.
142
- * @param fieldMask A mutable list of field paths encountered while parsing
143
- * the data.
144
148
*/
145
149
constructor (
146
150
readonly dataSource : UserDataSource ,
147
151
readonly methodName : string ,
148
152
readonly path : FieldPath | null ,
153
+ readonly arrayElement ?: boolean ,
149
154
fieldTransforms ?: FieldTransform [ ] ,
150
155
fieldMask ?: FieldPath [ ]
151
156
) {
@@ -154,36 +159,52 @@ class ParseContext {
154
159
if ( fieldTransforms === undefined ) {
155
160
this . validatePath ( ) ;
156
161
}
162
+ this . arrayElement = arrayElement !== undefined ? arrayElement : false ;
157
163
this . fieldTransforms = fieldTransforms || [ ] ;
158
164
this . fieldMask = fieldMask || [ ] ;
159
165
}
160
166
161
- childContext ( field : string | FieldPath | number ) : ParseContext {
162
- let childPath : FieldPath | null ;
163
- if ( typeof field === 'number' ) {
164
- // TODO(b/34871131): We don't support array paths right now; so make path
165
- // null.
166
- childPath = null ;
167
- } else {
168
- childPath = this . path == null ? null : this . path . child ( field ) ;
169
- }
167
+ childContextForField ( field : string ) : ParseContext {
168
+ const childPath = this . path == null ? null : this . path . child ( field ) ;
170
169
const context = new ParseContext (
171
170
this . dataSource ,
172
171
this . methodName ,
173
172
childPath ,
173
+ /*arrayElement=*/ false ,
174
174
this . fieldTransforms ,
175
175
this . fieldMask
176
176
) ;
177
- if ( typeof field === 'string' ) {
178
- // We only need to validate the new segment.
179
- context . validatePathSegment ( field ) ;
180
- } else if ( typeof field === 'object' && field instanceof FieldPath ) {
181
- // Validate the whole path.
182
- context . validatePath ( ) ;
183
- }
177
+ context . validatePathSegment ( field ) ;
184
178
return context ;
185
179
}
186
180
181
+ childContextForFieldPath ( field : FieldPath ) : ParseContext {
182
+ const childPath = this . path == null ? null : this . path . child ( field ) ;
183
+ const context = new ParseContext (
184
+ this . dataSource ,
185
+ this . methodName ,
186
+ childPath ,
187
+ /*arrayElement=*/ false ,
188
+ this . fieldTransforms ,
189
+ this . fieldMask
190
+ ) ;
191
+ context . validatePath ( ) ;
192
+ return context ;
193
+ }
194
+
195
+ childContextForArray ( index : number ) : ParseContext {
196
+ // TODO(b/34871131): We don't support array paths right now; so make path
197
+ // null.
198
+ return new ParseContext (
199
+ this . dataSource ,
200
+ this . methodName ,
201
+ /*path=*/ null ,
202
+ /*arrayElement=*/ true ,
203
+ this . fieldTransforms ,
204
+ this . fieldMask
205
+ ) ;
206
+ }
207
+
187
208
createError ( reason : string ) : Error {
188
209
const fieldDescription =
189
210
this . path === null || this . path . isEmpty ( )
@@ -272,7 +293,7 @@ export class UserDataConverter {
272
293
objUtils . forEach ( input as Dict < AnyJs > , ( key , value ) => {
273
294
const path = new ExternalFieldPath ( key ) . _internalPath ;
274
295
275
- const childContext = context . childContext ( path ) ;
296
+ const childContext = context . childContextForFieldPath ( path ) ;
276
297
value = this . runPreConverter ( value , childContext ) ;
277
298
278
299
const parsedValue = this . parseData ( value , childContext ) ;
@@ -299,7 +320,7 @@ export class UserDataConverter {
299
320
objUtils . forEach ( input as Dict < AnyJs > , ( key , value ) => {
300
321
const path = fieldPathFromDotSeparatedString ( methodName , key ) ;
301
322
302
- const childContext = context . childContext ( path ) ;
323
+ const childContext = context . childContextForFieldPath ( path ) ;
303
324
value = this . runPreConverter ( value , childContext ) ;
304
325
if ( value instanceof DeleteFieldValueImpl ) {
305
326
// Add it to the field mask, but don't add anything to updateData.
@@ -354,7 +375,7 @@ export class UserDataConverter {
354
375
355
376
for ( let i = 0 ; i < keys . length ; ++ i ) {
356
377
const path = keys [ i ] ;
357
- const childContext = context . childContext ( path ) ;
378
+ const childContext = context . childContextForFieldPath ( path ) ;
358
379
const value = this . runPreConverter ( values [ i ] , childContext ) ;
359
380
if ( value instanceof DeleteFieldValueImpl ) {
360
381
// Add it to the field mask, but don't add anything to updateData.
@@ -413,15 +434,16 @@ export class UserDataConverter {
413
434
private parseData ( input : AnyJs , context : ParseContext ) : FieldValue | null {
414
435
input = this . runPreConverter ( input , context ) ;
415
436
if ( input instanceof Array ) {
416
- // TODO(b/34871131): We may need a different way to detect nested arrays
417
- // once we support array paths (at which point we should include the path
418
- // containing the array in the error message).
419
- if ( ! context . path ) {
437
+ // TODO(b/34871131): Include the path containing the array in the error
438
+ // message.
439
+ if ( context . arrayElement ) {
420
440
throw context . createError ( 'Nested arrays are not supported' ) ;
421
441
}
422
- // We don't support field mask paths more granular than the top-level
423
- // array.
424
- context . fieldMask . push ( context . path ) ;
442
+ // If context.path is null we are already inside an array and we don't
443
+ // support field mask paths more granular than the top-level array.
444
+ if ( context . path ) {
445
+ context . fieldMask . push ( context . path ) ;
446
+ }
425
447
return this . parseArray ( input as AnyJs [ ] , context ) ;
426
448
} else if ( looksLikeJsonObject ( input ) ) {
427
449
validatePlainObject ( 'Unsupported field value:' , context , input ) ;
@@ -440,7 +462,10 @@ export class UserDataConverter {
440
462
const result = [ ] as FieldValue [ ] ;
441
463
let entryIndex = 0 ;
442
464
for ( const entry of array ) {
443
- let parsedEntry = this . parseData ( entry , context . childContext ( entryIndex ) ) ;
465
+ let parsedEntry = this . parseData (
466
+ entry ,
467
+ context . childContextForArray ( entryIndex )
468
+ ) ;
444
469
if ( parsedEntry == null ) {
445
470
// Just include nulls in the array for fields being replaced with a
446
471
// sentinel.
@@ -455,7 +480,10 @@ export class UserDataConverter {
455
480
private parseObject ( obj : Dict < AnyJs > , context : ParseContext ) : FieldValue {
456
481
let result = new SortedMap < string , FieldValue > ( primitiveComparator ) ;
457
482
objUtils . forEach ( obj , ( key : string , val : AnyJs ) => {
458
- const parsedValue = this . parseData ( val , context . childContext ( key ) ) ;
483
+ const parsedValue = this . parseData (
484
+ val ,
485
+ context . childContextForField ( key )
486
+ ) ;
459
487
if ( parsedValue != null ) {
460
488
result = result . insert ( key , parsedValue ) ;
461
489
}
0 commit comments