Repository navigation
Expand file tree
/
Copy pathprocessor.go
More file actions
180 lines (147 loc) · 6.12 KB
/
Copy pathprocessor.go
File metadata and controls
180 lines (147 loc) · 6.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Copyright 2021-2026 ALTESSA SOLUTIONS INC. All rights reserved.
// Use of this source code is governed by license that can be found in
// the LICENSE file.
// This file contains field processing logic for MongoDB document conversion.
// It encapsulates the documentFieldProcessor and related processing strategies
// to handle different field types during BSON document conversion.
package mongo
import (
"context"
"fmt"
"reflect"
"go.mongodb.org/mongo-driver/v2/bson"
coreerrs "github.com/altessa-s/go-atlas/core/errors"
)
// fieldProcessingResult holds the result of field processing
type fieldProcessingResult struct {
setDoc bson.M
unsetDoc bson.M
skip bool
}
// documentFieldProcessor encapsulates field processing logic with better separation of concerns
type documentFieldProcessor struct {
mongo *Mongo
ctx context.Context
meta fieldMetadata
update bool
result fieldProcessingResult
}
// applyPreProcessingFilters applies filters that might skip field processing
func (fp *documentFieldProcessor) applyPreProcessingFilters() fieldProcessingResult {
// Skip if field should be omitted on update
if fp.update && fp.meta.isOmitOnUpdate {
fp.result.skip = true
return fp.result
}
// Handle nil pointer fields in update mode
if fp.update && fp.meta.fieldValue.Kind() == reflect.Pointer && fp.meta.fieldValue.IsNil() {
fp.result.unsetDoc[fp.meta.fieldName] = nil
fp.result.skip = true
return fp.result
}
// Handle omit empty fields in insert mode
if !fp.update && fp.meta.isOmitEmpty && fp.meta.fieldValue.Kind() == reflect.Pointer &&
(fp.meta.fieldValue.IsNil() || reflect.Indirect(fp.meta.fieldValue).IsZero()) {
fp.result.skip = true
return fp.result
}
// Handle empty collections
if fp.mongo.isEmptyCollection(fp.meta.fieldValue) {
if fp.update {
fp.result.unsetDoc[fp.meta.fieldName] = nil
}
fp.result.skip = true
return fp.result
}
return fp.result
}
// processFieldDirect handles field processing with direct method dispatch instead of Strategy pattern
func (fp *documentFieldProcessor) processFieldDirect() (bson.M, bson.M, error) {
switch {
case fp.meta.fieldKind == reflect.Bool:
return fp.processBoolField()
case fp.mongo.isSliceField(fp.meta.fieldValue, fp.meta.fieldType, fp.update) &&
!fp.mongo.isBytesSlice(fp.meta.fieldValue):
return fp.processSliceField()
case fp.mongo.isStructPointerField(fp.meta.fieldValue, fp.meta.fieldType):
return fp.processStructPointerField()
case fp.mongo.isMapField(fp.meta.fieldValue, fp.meta.fieldType, fp.update):
return fp.processMapField()
default:
return fp.processDefaultField()
}
}
// Direct field processing methods (inline instead of strategies)
func (fp *documentFieldProcessor) processBoolField() (bson.M, bson.M, error) {
fp.result.setDoc[fp.meta.fieldName] = fp.meta.fieldValue.Bool()
return fp.result.setDoc, fp.result.unsetDoc, nil
}
func (fp *documentFieldProcessor) processSliceField() (bson.M, bson.M, error) {
return fp.mongo.processSliceField(fp.ctx, fp.meta, fp.update,
fp.result.setDoc, fp.result.unsetDoc)
}
func (fp *documentFieldProcessor) processStructPointerField() (bson.M, bson.M, error) {
return fp.mongo.processStructPointerField(fp.ctx, fp.meta, fp.update,
fp.result.setDoc, fp.result.unsetDoc)
}
func (fp *documentFieldProcessor) processMapField() (bson.M, bson.M, error) {
return fp.mongo.processMapField(fp.ctx, fp.meta, fp.update,
fp.result.setDoc, fp.result.unsetDoc)
}
func (fp *documentFieldProcessor) processDefaultField() (bson.M, bson.M, error) {
return fp.mongo.processDefaultField(fp.ctx, fp.meta, fp.result.setDoc)
}
// documentProcessor encapsulates document conversion logic with clear separation of concerns
type documentProcessor struct {
mongo *Mongo
}
// newDocumentProcessor creates a new document processor
func (m *Mongo) newDocumentProcessor() *documentProcessor {
return &documentProcessor{mongo: m}
}
// convertEntityToDocuments converts an entity to MongoDB documents with proper error handling
func (dp *documentProcessor) convertEntityToDocuments(ctx context.Context, entity any, update, encrypt bool) (bson.M, bson.M, error) {
// Step 1: Collect metadata
fieldsMetadata, err := dp.collectFieldsMetadataWithValidation(entity, update, encrypt)
if err != nil {
return nil, nil, coreerrs.Wrapf(err, "document conversion failed during %s", "metadata collection")
}
// Step 2: Process fields
setDoc, unsetDoc, err := dp.processFieldsWithErrorHandling(ctx, fieldsMetadata, update)
// Explicit cleanup to avoid defer closure allocations
dp.cleanupFieldMetadata(fieldsMetadata)
if err != nil {
return nil, nil, coreerrs.Wrapf(err, "document conversion failed during %s", "field processing")
}
return setDoc, unsetDoc, nil
}
// cleanupFieldMetadata returns fieldMetadata objects to pool without closure overhead
func (dp *documentProcessor) cleanupFieldMetadata(fieldsMetadata []fieldMetadata) {
// Note: fieldsMetadata contains values, not pointers from pool
// In our optimized parser, we return pooled objects immediately after use
// This function is for future extensibility if needed
}
// collectFieldsMetadataWithValidation collects field metadata with validation
func (dp *documentProcessor) collectFieldsMetadataWithValidation(entity any, update, encrypt bool) ([]fieldMetadata, error) {
if entity == nil {
return nil, fmt.Errorf("entity cannot be nil")
}
fieldsMetadata := dp.mongo.collectFieldsMetadata(entity, update, encrypt)
if len(fieldsMetadata) == 0 {
return nil, fmt.Errorf("entity contains no processable fields")
}
return fieldsMetadata, nil
}
// processFieldsWithErrorHandling processes fields with consistent error handling.
// Shared set/unset maps are passed directly to each field processor,
// eliminating per-field map allocation and merge overhead.
func (dp *documentProcessor) processFieldsWithErrorHandling(ctx context.Context, fieldsMetadata []fieldMetadata, update bool) (bson.M, bson.M, error) {
set := make(bson.M, len(fieldsMetadata))
unset := make(bson.M)
for _, meta := range fieldsMetadata {
if err := dp.mongo.processFieldInto(ctx, meta, update, set, unset); err != nil {
return nil, nil, coreerrs.WrapField(err, meta.fieldName)
}
}
return set, unset, nil
}