| 394 | } |
| 395 | |
| 396 | func walkAndMaskJSONRecursive(data any, objectSchema *storepb.ObjectSchema, semanticTypeToMasker map[string]masker.Masker) (any, error) { |
| 397 | if objectSchema == nil { |
| 398 | return data, nil |
| 399 | } |
| 400 | // Schema is ARRAY but data is a single element (e.g. field unwound by $unwind). |
| 401 | // When there is no array-level semantic type, descend into the item schema so |
| 402 | // that item-level masking still applies to the unwound scalar or object. |
| 403 | if objectSchema.Type == storepb.ObjectSchema_ARRAY && objectSchema.SemanticType == "" { |
| 404 | if _, isArray := data.([]any); !isArray { |
| 405 | if itemSchema := objectSchema.GetArrayKind().GetKind(); itemSchema != nil { |
| 406 | return walkAndMaskJSONRecursive(data, itemSchema, semanticTypeToMasker) |
| 407 | } |
| 408 | return data, nil |
| 409 | } |
| 410 | } |
| 411 | switch data := data.(type) { |
| 412 | case map[string]any: |
| 413 | if objectSchema.SemanticType != "" { |
| 414 | // If the semantic type is found, replace the entire value directly. |
| 415 | if m, ok := semanticTypeToMasker[objectSchema.SemanticType]; ok { |
| 416 | return applyMaskerToJSONMember(data, m) |
| 417 | } |
| 418 | } else { |
| 419 | // Otherwise, recursively walk the object. |
| 420 | structKind := objectSchema.GetStructKind() |
| 421 | // Quick return if there is no struct kind in object schema. |
| 422 | if structKind == nil { |
| 423 | return data, nil |
| 424 | } |
| 425 | for key, value := range data { |
| 426 | if childObjectSchema, ok := structKind.Properties[key]; ok { |
| 427 | // Recursively walk the property if child object schema found. |
| 428 | var err error |
| 429 | data[key], err = walkAndMaskJSONRecursive(value, childObjectSchema, semanticTypeToMasker) |
| 430 | if err != nil { |
| 431 | return nil, err |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | return data, nil |
| 437 | case []any: |
| 438 | if objectSchema.SemanticType != "" { |
| 439 | // If the semantic type is found, replace the entire value directly. |
| 440 | if m, ok := semanticTypeToMasker[objectSchema.SemanticType]; ok { |
| 441 | return applyMaskerToJSONMember(data, m) |
| 442 | } |
| 443 | } else { |
| 444 | arrayKind := objectSchema.GetArrayKind() |
| 445 | // Quick return if there is no array kind in object schema. |
| 446 | if arrayKind == nil { |
| 447 | return data, nil |
| 448 | } |
| 449 | childObjectSchema := arrayKind.GetKind() |
| 450 | if childObjectSchema == nil { |
| 451 | return data, nil |
| 452 | } |
| 453 | // Otherwise, recursively walk the array. |