• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

blevesearch / bleve / 21082842019

16 Jan 2026 10:28PM UTC coverage: 52.873% (+0.3%) from 52.621%
21082842019

Pull #2253

github

ajroetker
Add doublestar/v4 dependency for dynamic index templates

Required for glob pattern matching in dynamic index template
field mappings.
Pull Request #2253: (feat) Add dynamic templates for customizing dynamic field mappings

244 of 317 new or added lines in 3 files covered. (76.97%)

5 existing lines in 1 file now uncovered.

19427 of 36743 relevant lines covered (52.87%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.4
/mapping/document.go
1
//  Copyright (c) 2014 Couchbase, Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//                 http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package mapping
16

17
import (
18
        "encoding"
19
        "encoding/json"
20
        "fmt"
21
        "net"
22
        "reflect"
23
        "time"
24

25
        "github.com/blevesearch/bleve/v2/document"
26
        "github.com/blevesearch/bleve/v2/registry"
27
        "github.com/blevesearch/bleve/v2/util"
28
)
29

30
// A DocumentMapping describes how a type of document
31
// should be indexed.
32
// As documents can be hierarchical, named sub-sections
33
// of documents are mapped using the same structure in
34
// the Properties field.
35
// Each value inside a document can be indexed 0 or more
36
// ways.  These index entries are called fields and
37
// are stored in the Fields field.
38
// Entire sections of a document can be ignored or
39
// excluded by setting Enabled to false.
40
// If not explicitly mapped, default mapping operations
41
// are used.  To disable this automatic handling, set
42
// Dynamic to false.
43
type DocumentMapping struct {
44
        Enabled              bool                        `json:"enabled"`
45
        Dynamic              bool                        `json:"dynamic"`
46
        Properties           map[string]*DocumentMapping `json:"properties,omitempty"`
47
        Fields               []*FieldMapping             `json:"fields,omitempty"`
48
        Nested               bool                        `json:"nested,omitempty"`
49
        DefaultAnalyzer      string                      `json:"default_analyzer,omitempty"`
50
        DefaultSynonymSource string                      `json:"default_synonym_source,omitempty"`
51

52
        // DynamicTemplates define rules for mapping dynamically detected fields.
53
        // Templates are evaluated in order; the first matching template is used.
54
        // Templates are inherited from parent mappings but can be overridden.
55
        DynamicTemplates []*DynamicTemplate `json:"dynamic_templates,omitempty"`
56

57
        // StructTagKey overrides "json" when looking for field names in struct tags
58
        StructTagKey string `json:"struct_tag_key,omitempty"`
59
}
60

61
func (dm *DocumentMapping) Validate(cache *registry.Cache,
62
        path []string, fieldAliasCtx map[string]*FieldMapping,
63
) error {
×
64
        var err error
×
65
        if dm.DefaultAnalyzer != "" {
×
66
                _, err := cache.AnalyzerNamed(dm.DefaultAnalyzer)
×
67
                if err != nil {
×
68
                        return err
×
69
                }
×
70
        }
71
        if dm.DefaultSynonymSource != "" {
×
72
                _, err := cache.SynonymSourceNamed(dm.DefaultSynonymSource)
×
73
                if err != nil {
×
74
                        return err
×
75
                }
×
76
        }
77
        for propertyName, property := range dm.Properties {
×
78
                err = property.Validate(cache, append(path, propertyName), fieldAliasCtx)
×
79
                if err != nil {
×
80
                        return err
×
81
                }
×
82
        }
83
        for _, field := range dm.Fields {
×
84
                if field.Analyzer != "" {
×
85
                        _, err = cache.AnalyzerNamed(field.Analyzer)
×
86
                        if err != nil {
×
87
                                return err
×
88
                        }
×
89
                }
90
                if field.DateFormat != "" {
×
91
                        _, err = cache.DateTimeParserNamed(field.DateFormat)
×
92
                        if err != nil {
×
93
                                return err
×
94
                        }
×
95
                }
96
                if field.SynonymSource != "" {
×
97
                        _, err = cache.SynonymSourceNamed(field.SynonymSource)
×
98
                        if err != nil {
×
99
                                return err
×
100
                        }
×
101
                }
102
                err := validateFieldMapping(field, path, fieldAliasCtx)
×
103
                if err != nil {
×
104
                        return err
×
105
                }
×
106
        }
107
        return nil
×
108
}
109

110
func validateFieldType(field *FieldMapping) error {
×
111
        switch field.Type {
×
112
        case "text", "datetime", "number", "boolean", "geopoint", "geoshape", "IP":
×
113
                return nil
×
114
        default:
×
115
                return fmt.Errorf("field: '%s', unknown field type: '%s'",
×
116
                        field.Name, field.Type)
×
117
        }
118
}
119

120
// analyzerNameForPath attempts to first find the field
121
// described by this path, then returns the analyzer
122
// configured for that field
123
func (dm *DocumentMapping) analyzerNameForPath(path string) string {
1✔
124
        field := dm.fieldDescribedByPath(path)
1✔
125
        if field != nil {
2✔
126
                return field.Analyzer
1✔
127
        }
1✔
128
        return ""
×
129
}
130

131
// synonymSourceForPath attempts to first find the field
132
// described by this path, then returns the analyzer
133
// configured for that field
134
func (dm *DocumentMapping) synonymSourceForPath(path string) string {
×
135
        field := dm.fieldDescribedByPath(path)
×
136
        if field != nil {
×
137
                return field.SynonymSource
×
138
        }
×
139
        return ""
×
140
}
141

142
func (dm *DocumentMapping) fieldDescribedByPath(path string) *FieldMapping {
1✔
143
        pathElements := decodePath(path)
1✔
144
        if len(pathElements) > 1 {
2✔
145
                // easy case, there is more than 1 path element remaining
1✔
146
                // the next path element must match a property name
1✔
147
                // at this level
1✔
148
                for propName, subDocMapping := range dm.Properties {
2✔
149
                        if propName == pathElements[0] {
2✔
150
                                return subDocMapping.fieldDescribedByPath(encodePath(pathElements[1:]))
1✔
151
                        }
1✔
152
                }
153
        }
154

155
        // either the path just had one element
156
        // or it had multiple, but no match for the first element at this level
157
        // look for match with full path
158

159
        // first look for property name with empty field
160
        for propName, subDocMapping := range dm.Properties {
2✔
161
                if propName == path {
2✔
162
                        // found property name match, now look at its fields
1✔
163
                        for _, field := range subDocMapping.Fields {
2✔
164
                                if field.Name == "" || field.Name == path {
2✔
165
                                        // match
1✔
166
                                        return field
1✔
167
                                }
1✔
168
                        }
169
                }
170
        }
171
        // next, walk the properties again, looking for field overriding the name
172
        for propName, subDocMapping := range dm.Properties {
2✔
173
                if propName != path {
2✔
174
                        // property name isn't a match, but field name could override it
1✔
175
                        for _, field := range subDocMapping.Fields {
2✔
176
                                if field.Name == path {
2✔
177
                                        return field
1✔
178
                                }
1✔
179
                        }
180
                }
181
        }
182

183
        return nil
×
184
}
185

186
// documentMappingForPathElements returns the EXACT and closest matches for a sub
187
// document or for an explicitly mapped field; the closest most specific
188
// document mapping could be one that matches part of the provided path.
189
func (dm *DocumentMapping) documentMappingForPathElements(pathElements []string) (
190
        *DocumentMapping, *DocumentMapping,
191
) {
1✔
192
        var pathElementsCopy []string
1✔
193
        if len(pathElements) == 0 {
2✔
194
                pathElementsCopy = []string{""}
1✔
195
        } else {
2✔
196
                pathElementsCopy = pathElements
1✔
197
        }
1✔
198
        current := dm
1✔
199
OUTER:
1✔
200
        for i, pathElement := range pathElementsCopy {
2✔
201
                if subDocMapping, exists := current.Properties[pathElement]; exists {
2✔
202
                        current = subDocMapping
1✔
203
                        continue OUTER
1✔
204
                }
205

206
                // no subDocMapping matches this pathElement
207
                // only if this is the last element check for field name
208
                if i == len(pathElementsCopy)-1 {
2✔
209
                        for _, field := range current.Fields {
2✔
210
                                if field.Name == pathElement {
1✔
211
                                        break
×
212
                                }
213
                        }
214
                }
215

216
                return nil, current
1✔
217
        }
218
        return current, current
1✔
219
}
220

221
// documentMappingForPath returns the EXACT and closest matches for a sub
222
// document or for an explicitly mapped field; the closest most specific
223
// document mapping could be one that matches part of the provided path.
224
func (dm *DocumentMapping) documentMappingForPath(path string) (
225
        *DocumentMapping, *DocumentMapping,
226
) {
1✔
227
        pathElements := decodePath(path)
1✔
228
        return dm.documentMappingForPathElements(pathElements)
1✔
229
}
1✔
230

231
// NewDocumentMapping returns a new document mapping
232
// with all the default values.
233
func NewDocumentMapping() *DocumentMapping {
1✔
234
        return &DocumentMapping{
1✔
235
                Enabled: true,
1✔
236
                Dynamic: true,
1✔
237
        }
1✔
238
}
1✔
239

240
// NewNestedDocumentMapping returns a new document
241
// mapping that treats sub-documents as nested
242
// objects.
243
func NewNestedDocumentMapping() *DocumentMapping {
×
244
        return &DocumentMapping{
×
245
                Nested:  true,
×
246
                Enabled: true,
×
247
                Dynamic: true,
×
248
        }
×
249
}
×
250

251
// NewDocumentStaticMapping returns a new document
252
// mapping that will not automatically index parts
253
// of a document without an explicit mapping.
254
func NewDocumentStaticMapping() *DocumentMapping {
1✔
255
        return &DocumentMapping{
1✔
256
                Enabled: true,
1✔
257
        }
1✔
258
}
1✔
259

260
// NewNestedDocumentStaticMapping returns a new document
261
// mapping that treats sub-documents as nested
262
// objects and will not automatically index parts
263
// of the nested document without an explicit mapping.
264
func NewNestedDocumentStaticMapping() *DocumentMapping {
×
265
        return &DocumentMapping{
×
266
                Enabled: true,
×
267
                Nested:  true,
×
268
        }
×
269
}
×
270

271
// NewDocumentDisabledMapping returns a new document
272
// mapping that will not perform any indexing.
273
func NewDocumentDisabledMapping() *DocumentMapping {
×
274
        return &DocumentMapping{}
×
275
}
×
276

277
// AddSubDocumentMapping adds the provided DocumentMapping as a sub-mapping
278
// for the specified named subsection.
279
func (dm *DocumentMapping) AddSubDocumentMapping(property string, sdm *DocumentMapping) {
1✔
280
        if dm.Properties == nil {
2✔
281
                dm.Properties = make(map[string]*DocumentMapping)
1✔
282
        }
1✔
283
        dm.Properties[property] = sdm
1✔
284
}
285

286
// AddFieldMappingsAt adds one or more FieldMappings
287
// at the named sub-document.  If the named sub-document
288
// doesn't yet exist it is created for you.
289
// This is a convenience function to make most common
290
// mappings more concise.
291
// Otherwise, you would:
292
//
293
//        subMapping := NewDocumentMapping()
294
//        subMapping.AddFieldMapping(fieldMapping)
295
//        parentMapping.AddSubDocumentMapping(property, subMapping)
296
func (dm *DocumentMapping) AddFieldMappingsAt(property string, fms ...*FieldMapping) {
1✔
297
        if dm.Properties == nil {
2✔
298
                dm.Properties = make(map[string]*DocumentMapping)
1✔
299
        }
1✔
300
        sdm, ok := dm.Properties[property]
1✔
301
        if !ok {
2✔
302
                sdm = NewDocumentMapping()
1✔
303
        }
1✔
304
        for _, fm := range fms {
2✔
305
                sdm.AddFieldMapping(fm)
1✔
306
        }
1✔
307
        dm.Properties[property] = sdm
1✔
308
}
309

310
// AddFieldMapping adds the provided FieldMapping for this section
311
// of the document.
312
func (dm *DocumentMapping) AddFieldMapping(fm *FieldMapping) {
1✔
313
        if dm.Fields == nil {
2✔
314
                dm.Fields = make([]*FieldMapping, 0)
1✔
315
        }
1✔
316
        dm.Fields = append(dm.Fields, fm)
1✔
317
}
318

319
// AddDynamicTemplate adds a dynamic template to this document mapping.
320
// Templates are evaluated in order when mapping dynamic fields.
321
func (dm *DocumentMapping) AddDynamicTemplate(template *DynamicTemplate) {
1✔
322
        if dm.DynamicTemplates == nil {
2✔
323
                dm.DynamicTemplates = make([]*DynamicTemplate, 0)
1✔
324
        }
1✔
325
        dm.DynamicTemplates = append(dm.DynamicTemplates, template)
1✔
326
}
327

328
// findMatchingTemplate searches for a matching dynamic template for a field.
329
// It checks templates at the current mapping level first, then inherits from
330
// parent templates if no local match is found.
331
// Parameters:
332
//   - fieldName: the name of the field (last path element)
333
//   - pathStr: the full dotted path to the field
334
//   - detectedType: the detected type of the field value ("string", "number", etc.)
335
//   - parentTemplates: templates inherited from parent document mappings
336
func (dm *DocumentMapping) findMatchingTemplate(fieldName, pathStr, detectedType string,
337
        parentTemplates []*DynamicTemplate) *DynamicTemplate {
1✔
338
        // Check own templates first (local templates take precedence)
1✔
339
        for _, template := range dm.DynamicTemplates {
2✔
340
                if template.Matches(fieldName, pathStr, detectedType) {
2✔
341
                        return template
1✔
342
                }
1✔
343
        }
344

345
        // Fall back to parent templates (inheritance)
346
        for _, template := range parentTemplates {
2✔
347
                if template.Matches(fieldName, pathStr, detectedType) {
2✔
348
                        return template
1✔
349
                }
1✔
350
        }
351

352
        return nil
1✔
353
}
354

355
// collectTemplatesAlongPath collects all dynamic templates along the path to a field.
356
// Templates from parent mappings are collected first, so child mappings can override.
357
// This collects templates from all ancestor mappings but NOT the closest mapping
358
// (which is checked separately in findMatchingTemplate).
359
func (dm *DocumentMapping) collectTemplatesAlongPath(pathElements []string) []*DynamicTemplate {
1✔
360
        var templates []*DynamicTemplate
1✔
361

1✔
362
        // Start with templates at the root
1✔
363
        templates = append(templates, dm.DynamicTemplates...)
1✔
364

1✔
365
        // Walk down the path, collecting templates from each level EXCEPT the closest one.
1✔
366
        // For path ["a", "b", "field"], we want templates from root and "a", but not "b"
1✔
367
        // because "b" is the closest mapping and will be checked separately.
1✔
368
        current := dm
1✔
369
        stopAt := max(0, len(pathElements)-2)
1✔
370
        for i, pathElement := range pathElements {
2✔
371
                if i >= stopAt {
2✔
372
                        break
1✔
373
                }
374
                if subDocMapping, exists := current.Properties[pathElement]; exists {
2✔
375
                        templates = append(templates, subDocMapping.DynamicTemplates...)
1✔
376
                        current = subDocMapping
1✔
377
                } else {
2✔
378
                        break
1✔
379
                }
380
        }
381

382
        return templates
1✔
383
}
384

385
// UnmarshalJSON offers custom unmarshaling with optional strict validation
386
func (dm *DocumentMapping) UnmarshalJSON(data []byte) error {
1✔
387
        var tmp map[string]json.RawMessage
1✔
388
        err := util.UnmarshalJSON(data, &tmp)
1✔
389
        if err != nil {
1✔
390
                return err
×
391
        }
×
392

393
        // set defaults for fields which might have been omitted
394
        dm.Enabled = true
1✔
395
        dm.Dynamic = true
1✔
396

1✔
397
        var invalidKeys []string
1✔
398
        for k, v := range tmp {
2✔
399
                switch k {
1✔
400
                case "enabled":
1✔
401
                        err := util.UnmarshalJSON(v, &dm.Enabled)
1✔
402
                        if err != nil {
1✔
403
                                return err
×
404
                        }
×
405
                case "dynamic":
1✔
406
                        err := util.UnmarshalJSON(v, &dm.Dynamic)
1✔
407
                        if err != nil {
1✔
408
                                return err
×
409
                        }
×
410
                case "nested":
×
411
                        err := util.UnmarshalJSON(v, &dm.Nested)
×
412
                        if err != nil {
×
413
                                return err
×
414
                        }
×
415
                case "default_analyzer":
×
416
                        err := util.UnmarshalJSON(v, &dm.DefaultAnalyzer)
×
417
                        if err != nil {
×
418
                                return err
×
419
                        }
×
420
                case "default_synonym_source":
×
421
                        err := util.UnmarshalJSON(v, &dm.DefaultSynonymSource)
×
422
                        if err != nil {
×
423
                                return err
×
424
                        }
×
425
                case "properties":
1✔
426
                        err := util.UnmarshalJSON(v, &dm.Properties)
1✔
427
                        if err != nil {
1✔
428
                                return err
×
429
                        }
×
430
                case "fields":
1✔
431
                        err := util.UnmarshalJSON(v, &dm.Fields)
1✔
432
                        if err != nil {
1✔
433
                                return err
×
434
                        }
×
435
                case "struct_tag_key":
×
436
                        err := util.UnmarshalJSON(v, &dm.StructTagKey)
×
437
                        if err != nil {
×
438
                                return err
×
439
                        }
×
440
                case "dynamic_templates":
1✔
441
                        err := util.UnmarshalJSON(v, &dm.DynamicTemplates)
1✔
442
                        if err != nil {
1✔
NEW
443
                                return err
×
NEW
444
                        }
×
445
                default:
1✔
446
                        invalidKeys = append(invalidKeys, k)
1✔
447
                }
448
        }
449

450
        if MappingJSONStrict && len(invalidKeys) > 0 {
2✔
451
                return fmt.Errorf("document mapping contains invalid keys: %v", invalidKeys)
1✔
452
        }
1✔
453

454
        return nil
1✔
455
}
456

457
func (dm *DocumentMapping) defaultAnalyzerName(path []string) string {
1✔
458
        current := dm
1✔
459
        rv := current.DefaultAnalyzer
1✔
460
        for _, pathElement := range path {
2✔
461
                var ok bool
1✔
462
                current, ok = current.Properties[pathElement]
1✔
463
                if !ok {
2✔
464
                        break
1✔
465
                }
466
                if current.DefaultAnalyzer != "" {
1✔
467
                        rv = current.DefaultAnalyzer
×
468
                }
×
469
        }
470
        return rv
1✔
471
}
472

473
func (dm *DocumentMapping) defaultSynonymSource(path []string) string {
×
474
        current := dm
×
475
        rv := current.DefaultSynonymSource
×
476
        for _, pathElement := range path {
×
477
                var ok bool
×
478
                current, ok = current.Properties[pathElement]
×
479
                if !ok {
×
480
                        break
×
481
                }
482
                if current.DefaultSynonymSource != "" {
×
483
                        rv = current.DefaultSynonymSource
×
484
                }
×
485
        }
486
        return rv
×
487
}
488

489
// baseType returns the base type of v by dereferencing pointers
490
func baseType(v interface{}) reflect.Type {
×
491
        if v == nil {
×
492
                return nil
×
493
        }
×
494
        t := reflect.TypeOf(v)
×
495
        for t.Kind() == reflect.Pointer {
×
496
                t = t.Elem()
×
497
        }
×
498
        return t
×
499
}
500

501
func (dm *DocumentMapping) walkDocument(data interface{}, path []string, indexes []uint64, context *walkContext) {
1✔
502
        // allow default "json" tag to be overridden
1✔
503
        structTagKey := dm.StructTagKey
1✔
504
        if structTagKey == "" {
2✔
505
                structTagKey = "json"
1✔
506
        }
1✔
507

508
        val := reflect.ValueOf(data)
1✔
509
        if !val.IsValid() {
1✔
510
                return
×
511
        }
×
512

513
        typ := val.Type()
1✔
514
        switch typ.Kind() {
1✔
515
        case reflect.Map:
1✔
516
                // FIXME can add support for other map keys in the future
1✔
517
                if typ.Key().Kind() == reflect.String {
2✔
518
                        for _, key := range val.MapKeys() {
2✔
519
                                fieldName := key.String()
1✔
520
                                fieldVal := val.MapIndex(key).Interface()
1✔
521
                                dm.processProperty(fieldVal, append(path, fieldName), indexes, context)
1✔
522
                        }
1✔
523
                }
524
        case reflect.Struct:
1✔
525
                for i := 0; i < val.NumField(); i++ {
2✔
526
                        field := typ.Field(i)
1✔
527
                        fieldName := field.Name
1✔
528
                        // anonymous fields of type struct can elide the type name
1✔
529
                        if field.Anonymous && field.Type.Kind() == reflect.Struct {
2✔
530
                                fieldName = ""
1✔
531
                        }
1✔
532

533
                        // if the field has a name under the specified tag, prefer that
534
                        tag := field.Tag.Get(structTagKey)
1✔
535
                        tagFieldName := parseTagName(tag)
1✔
536
                        if tagFieldName == "-" {
2✔
537
                                continue
1✔
538
                        }
539
                        // allow tag to set field name to empty, only if anonymous
540
                        if field.Tag != "" && (tagFieldName != "" || field.Anonymous) {
2✔
541
                                fieldName = tagFieldName
1✔
542
                        }
1✔
543

544
                        if val.Field(i).CanInterface() {
2✔
545
                                fieldVal := val.Field(i).Interface()
1✔
546
                                newpath := path
1✔
547
                                if fieldName != "" {
2✔
548
                                        newpath = append(path, fieldName)
1✔
549
                                }
1✔
550
                                dm.processProperty(fieldVal, newpath, indexes, context)
1✔
551
                        }
552
                }
553
        case reflect.Slice, reflect.Array:
1✔
554
                subDocMapping, _ := dm.documentMappingForPathElements(path)
1✔
555
                allowNested := subDocMapping != nil && subDocMapping.Nested
1✔
556
                for i := 0; i < val.Len(); i++ {
2✔
557
                        // for each array element, check if it can be represented as an interface
1✔
558
                        idxVal := val.Index(i)
1✔
559
                        // skip invalid values
1✔
560
                        if !idxVal.CanInterface() {
1✔
561
                                continue
×
562
                        }
563
                        // get the actual value in interface form
564
                        actual := idxVal.Interface()
1✔
565
                        // if nested mapping, only create nested document for object elements
1✔
566
                        if allowNested && actual != nil {
1✔
567
                                // check the kind of the actual value, is it an object (struct or map)?
×
568
                                typ := baseType(actual)
×
569
                                if typ == nil {
×
570
                                        continue
×
571
                                }
572
                                kind := typ.Kind()
×
573
                                // only create nested docs for real JSON objects
×
574
                                if kind == reflect.Struct || kind == reflect.Map {
×
575
                                        // Create nested document only for only object elements
×
576
                                        nestedDocument := document.NewDocument(
×
577
                                                fmt.Sprintf("%s_$%s_$%d", context.doc.ID(), encodePath(path), i))
×
578
                                        nestedContext := context.im.newWalkContext(nestedDocument, dm)
×
579
                                        dm.processProperty(actual, path, append(indexes, uint64(i)), nestedContext)
×
580
                                        context.doc.AddNestedDocument(nestedDocument)
×
581
                                        continue
×
582
                                }
583
                        }
584
                        // non-nested mapping, or non-object element in nested mapping
585
                        // process the element normally
586
                        dm.processProperty(actual, path, append(indexes, uint64(i)), context)
1✔
587
                }
588
        case reflect.Ptr:
1✔
589
                ptrElem := val.Elem()
1✔
590
                if ptrElem.IsValid() && ptrElem.CanInterface() {
2✔
591
                        dm.processProperty(ptrElem.Interface(), path, indexes, context)
1✔
592
                }
1✔
593
        case reflect.String:
1✔
594
                dm.processProperty(val.String(), path, indexes, context)
1✔
595
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
1✔
596
                dm.processProperty(float64(val.Int()), path, indexes, context)
1✔
597
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
1✔
598
                dm.processProperty(float64(val.Uint()), path, indexes, context)
1✔
599
        case reflect.Float32, reflect.Float64:
1✔
600
                dm.processProperty(float64(val.Float()), path, indexes, context)
1✔
601
        case reflect.Bool:
1✔
602
                dm.processProperty(val.Bool(), path, indexes, context)
1✔
603
        }
604
}
605

606
func (dm *DocumentMapping) processProperty(property interface{}, path []string, indexes []uint64, context *walkContext) {
1✔
607
        // look to see if there is a mapping for this field
1✔
608
        subDocMapping, closestDocMapping := dm.documentMappingForPathElements(path)
1✔
609

1✔
610
        // check to see if we even need to do further processing
1✔
611
        if subDocMapping != nil && !subDocMapping.Enabled {
1✔
612
                return
×
613
        }
×
614

615
        propertyValue := reflect.ValueOf(property)
1✔
616
        if !propertyValue.IsValid() {
2✔
617
                // cannot do anything with the zero value
1✔
618
                return
1✔
619
        }
1✔
620

621
        pathString := encodePath(path)
1✔
622
        propertyType := propertyValue.Type()
1✔
623
        switch propertyType.Kind() {
1✔
624
        case reflect.String:
1✔
625
                propertyValueString := propertyValue.String()
1✔
626
                if subDocMapping != nil {
2✔
627
                        // index by explicit mapping
1✔
628
                        for _, fieldMapping := range subDocMapping.Fields {
2✔
629
                                switch fieldMapping.Type {
1✔
630
                                case "geoshape":
×
631
                                        fieldMapping.processGeoShape(property, pathString, path, indexes, context)
×
632
                                case "geopoint":
1✔
633
                                        fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
1✔
634
                                case "vector_base64":
×
635
                                        fieldMapping.processVectorBase64(property, pathString, path, indexes, context)
×
636
                                default:
1✔
637
                                        fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
1✔
638
                                }
639
                        }
640
                } else if closestDocMapping.Dynamic {
2✔
641
                        // automatic indexing behavior
1✔
642

1✔
643
                        // Check for a matching dynamic template first
1✔
644
                        fieldName := ""
1✔
645
                        if len(path) > 0 {
2✔
646
                                fieldName = path[len(path)-1]
1✔
647
                        }
1✔
648
                        parentTemplates := dm.collectTemplatesAlongPath(path)
1✔
649
                        template := closestDocMapping.findMatchingTemplate(fieldName, pathString, "string", parentTemplates)
1✔
650

1✔
651
                        if template != nil && template.Mapping != nil {
2✔
652
                                // Use the template's mapping with dynamic defaults
1✔
653
                                fieldMapping := applyDynamicDefaults(template.Mapping, context.im)
1✔
654
                                switch fieldMapping.Type {
1✔
NEW
655
                                case "datetime":
×
NEW
656
                                        dateTimeParser := context.im.DateTimeParserNamed(context.im.DefaultDateTimeParser)
×
NEW
657
                                        if dateTimeParser != nil {
×
NEW
658
                                                parsedDateTime, layout, err := dateTimeParser.ParseDateTime(propertyValueString)
×
NEW
659
                                                if err == nil {
×
NEW
660
                                                        fieldMapping.processTime(parsedDateTime, layout, pathString, path, indexes, context)
×
NEW
661
                                                }
×
662
                                        }
663
                                default:
1✔
664
                                        fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
1✔
665
                                }
666
                        } else {
1✔
667
                                // Default behavior: first see if it can be parsed by the default date parser
1✔
668
                                dateTimeParser := context.im.DateTimeParserNamed(context.im.DefaultDateTimeParser)
1✔
669
                                if dateTimeParser != nil {
2✔
670
                                        parsedDateTime, layout, err := dateTimeParser.ParseDateTime(propertyValueString)
1✔
671
                                        if err != nil {
2✔
672
                                                // index as text
1✔
673
                                                fieldMapping := newTextFieldMappingDynamic(context.im)
1✔
674
                                                fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
1✔
675
                                        } else {
1✔
NEW
676
                                                // index as datetime
×
NEW
677
                                                fieldMapping := newDateTimeFieldMappingDynamic(context.im)
×
NEW
678
                                                fieldMapping.processTime(parsedDateTime, layout, pathString, path, indexes, context)
×
NEW
679
                                        }
×
680
                                }
681
                        }
682
                }
683
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
1✔
684
                dm.processProperty(float64(propertyValue.Int()), path, indexes, context)
1✔
685
                return
1✔
686
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
×
687
                dm.processProperty(float64(propertyValue.Uint()), path, indexes, context)
×
688
                return
×
689
        case reflect.Float64, reflect.Float32:
1✔
690
                propertyValFloat := propertyValue.Float()
1✔
691
                if subDocMapping != nil {
2✔
692
                        // index by explicit mapping
1✔
693
                        for _, fieldMapping := range subDocMapping.Fields {
2✔
694
                                fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
1✔
695
                        }
1✔
696
                } else if closestDocMapping.Dynamic {
2✔
697
                        // Check for a matching dynamic template first
1✔
698
                        fieldName := ""
1✔
699
                        if len(path) > 0 {
2✔
700
                                fieldName = path[len(path)-1]
1✔
701
                        }
1✔
702
                        parentTemplates := dm.collectTemplatesAlongPath(path)
1✔
703
                        template := closestDocMapping.findMatchingTemplate(fieldName, pathString, "number", parentTemplates)
1✔
704

1✔
705
                        if template != nil && template.Mapping != nil {
2✔
706
                                // Use the template's mapping with dynamic defaults
1✔
707
                                fieldMapping := applyDynamicDefaults(template.Mapping, context.im)
1✔
708
                                fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
1✔
709
                        } else {
2✔
710
                                // automatic indexing behavior
1✔
711
                                fieldMapping := newNumericFieldMappingDynamic(context.im)
1✔
712
                                fieldMapping.processFloat64(propertyValFloat, pathString, path, indexes, context)
1✔
713
                        }
1✔
714
                }
715
        case reflect.Bool:
1✔
716
                propertyValBool := propertyValue.Bool()
1✔
717
                if subDocMapping != nil {
1✔
718
                        // index by explicit mapping
×
719
                        for _, fieldMapping := range subDocMapping.Fields {
×
720
                                fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
×
721
                        }
×
722
                } else if closestDocMapping.Dynamic {
2✔
723
                        // Check for a matching dynamic template first
1✔
724
                        fieldName := ""
1✔
725
                        if len(path) > 0 {
2✔
726
                                fieldName = path[len(path)-1]
1✔
727
                        }
1✔
728
                        parentTemplates := dm.collectTemplatesAlongPath(path)
1✔
729
                        template := closestDocMapping.findMatchingTemplate(fieldName, pathString, "boolean", parentTemplates)
1✔
730

1✔
731
                        if template != nil && template.Mapping != nil {
2✔
732
                                // Use the template's mapping with dynamic defaults
1✔
733
                                fieldMapping := applyDynamicDefaults(template.Mapping, context.im)
1✔
734
                                fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
1✔
735
                        } else {
2✔
736
                                // automatic indexing behavior
1✔
737
                                fieldMapping := newBooleanFieldMappingDynamic(context.im)
1✔
738
                                fieldMapping.processBoolean(propertyValBool, pathString, path, indexes, context)
1✔
739
                        }
1✔
740
                }
741
        case reflect.Struct:
1✔
742
                switch property := property.(type) {
1✔
743
                case time.Time:
1✔
744
                        // don't descend into the time struct
1✔
745
                        if subDocMapping != nil {
2✔
746
                                // index by explicit mapping
1✔
747
                                for _, fieldMapping := range subDocMapping.Fields {
2✔
748
                                        fieldMapping.processTime(property, time.RFC3339, pathString, path, indexes, context)
1✔
749
                                }
1✔
750
                        } else if closestDocMapping.Dynamic {
2✔
751
                                // Check for a matching dynamic template first
1✔
752
                                fieldName := ""
1✔
753
                                if len(path) > 0 {
2✔
754
                                        fieldName = path[len(path)-1]
1✔
755
                                }
1✔
756
                                parentTemplates := dm.collectTemplatesAlongPath(path)
1✔
757
                                template := closestDocMapping.findMatchingTemplate(fieldName, pathString, "date", parentTemplates)
1✔
758

1✔
759
                                if template != nil && template.Mapping != nil {
2✔
760
                                        // Use the template's mapping with dynamic defaults
1✔
761
                                        fieldMapping := applyDynamicDefaults(template.Mapping, context.im)
1✔
762
                                        fieldMapping.processTime(property, time.RFC3339, pathString, path, indexes, context)
1✔
763
                                } else {
2✔
764
                                        fieldMapping := newDateTimeFieldMappingDynamic(context.im)
1✔
765
                                        fieldMapping.processTime(property, time.RFC3339, pathString, path, indexes, context)
1✔
766
                                }
1✔
767
                        }
768
                case encoding.TextMarshaler:
×
769
                        txt, err := property.MarshalText()
×
770
                        if err == nil && subDocMapping != nil {
×
771
                                // index by explicit mapping
×
772
                                for _, fieldMapping := range subDocMapping.Fields {
×
773
                                        if fieldMapping.Type == "text" {
×
774
                                                fieldMapping.processString(string(txt), pathString, path, indexes, context)
×
775
                                        }
×
776
                                }
777
                        }
778
                        dm.walkDocument(property, path, indexes, context)
×
779
                default:
1✔
780
                        if subDocMapping != nil {
2✔
781
                                for _, fieldMapping := range subDocMapping.Fields {
2✔
782
                                        switch fieldMapping.Type {
1✔
783
                                        case "geopoint":
1✔
784
                                                fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
1✔
785
                                        case "geoshape":
×
786
                                                fieldMapping.processGeoShape(property, pathString, path, indexes, context)
×
787
                                        }
788
                                }
789
                        }
790
                        dm.walkDocument(property, path, indexes, context)
1✔
791
                }
792
        case reflect.Map, reflect.Slice:
1✔
793
                walkDocument := false
1✔
794
                if subDocMapping != nil && len(subDocMapping.Fields) != 0 {
2✔
795
                        for _, fieldMapping := range subDocMapping.Fields {
2✔
796
                                switch fieldMapping.Type {
1✔
797
                                case "vector":
×
798
                                        fieldMapping.processVector(property, pathString, path,
×
799
                                                indexes, context)
×
800
                                case "geopoint":
1✔
801
                                        fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
1✔
802
                                        walkDocument = true
1✔
803
                                case "IP":
×
804
                                        ip, ok := property.(net.IP)
×
805
                                        if ok {
×
806
                                                fieldMapping.processIP(ip, pathString, path, indexes, context)
×
807
                                        }
×
808
                                        walkDocument = true
×
809
                                case "geoshape":
×
810
                                        fieldMapping.processGeoShape(property, pathString, path, indexes, context)
×
811
                                        walkDocument = true
×
812
                                default:
×
813
                                        walkDocument = true
×
814
                                }
815
                        }
816
                } else {
1✔
817
                        walkDocument = true
1✔
818
                }
1✔
819
                if walkDocument {
2✔
820
                        dm.walkDocument(property, path, indexes, context)
1✔
821
                }
1✔
822
        case reflect.Ptr:
1✔
823
                if !propertyValue.IsNil() {
2✔
824
                        switch property := property.(type) {
1✔
825
                        case encoding.TextMarshaler:
1✔
826
                                // ONLY process TextMarshaler if there is an explicit mapping
1✔
827
                                // AND all of the fields are of type text
1✔
828
                                // OTHERWISE process field without TextMarshaler
1✔
829
                                if subDocMapping != nil {
2✔
830
                                        allFieldsText := true
1✔
831
                                        for _, fieldMapping := range subDocMapping.Fields {
2✔
832
                                                if fieldMapping.Type != "text" {
2✔
833
                                                        allFieldsText = false
1✔
834
                                                        break
1✔
835
                                                }
836
                                        }
837
                                        txt, err := property.MarshalText()
1✔
838
                                        if err == nil && allFieldsText {
2✔
839
                                                txtStr := string(txt)
1✔
840
                                                for _, fieldMapping := range subDocMapping.Fields {
2✔
841
                                                        fieldMapping.processString(txtStr, pathString, path, indexes, context)
1✔
842
                                                }
1✔
843
                                                return
1✔
844
                                        }
845
                                }
846
                                dm.walkDocument(property, path, indexes, context)
1✔
847
                        default:
1✔
848
                                dm.walkDocument(property, path, indexes, context)
1✔
849
                        }
850
                }
851
        default:
×
852
                dm.walkDocument(property, path, indexes, context)
×
853
        }
854
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc