Skip to content
This repository was archived by the owner on Aug 30, 2025. It is now read-only.

Commit 5e15935

Browse files
NEOS-1351 Add mongo db workflow integration test (#2515)
1 parent 3fc2369 commit 5e15935

File tree

18 files changed

+1105
-135
lines changed

18 files changed

+1105
-135
lines changed

backend/pkg/mongomanager/utils.go

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
package mongomanager
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"math"
8+
"math/big"
9+
"reflect"
10+
"strconv"
11+
"time"
12+
13+
neosync_types "github.com/nucleuscloud/neosync/internal/types"
14+
"go.mongodb.org/mongo-driver/bson"
15+
"go.mongodb.org/mongo-driver/bson/primitive"
16+
)
17+
18+
func UnmarshalPrimitives(doc map[string]any) (standardMap map[string]any, keyTypeMap map[string]neosync_types.KeyType) {
19+
result := make(map[string]any)
20+
ktm := make(map[string]neosync_types.KeyType)
21+
for k, v := range doc {
22+
result[k] = ParsePrimitives(k, v, ktm)
23+
}
24+
return result, ktm
25+
}
26+
27+
func ParsePrimitives(key string, value any, keyTypeMap map[string]neosync_types.KeyType) any {
28+
switch v := value.(type) {
29+
case primitive.Decimal128:
30+
keyTypeMap[key] = neosync_types.Decimal128
31+
floatVal, _, err := big.ParseFloat(v.String(), 10, 128, big.ToNearestEven)
32+
if err == nil {
33+
return floatVal
34+
}
35+
return v
36+
case primitive.Binary:
37+
keyTypeMap[key] = neosync_types.Binary
38+
return v
39+
case primitive.ObjectID:
40+
keyTypeMap[key] = neosync_types.ObjectID
41+
return v
42+
case primitive.Timestamp:
43+
keyTypeMap[key] = neosync_types.Timestamp
44+
return v
45+
case bson.D:
46+
m := make(map[string]any)
47+
for _, elem := range v {
48+
path := elem.Key
49+
if key != "" {
50+
path = fmt.Sprintf("%s.%s", key, elem.Key)
51+
}
52+
m[elem.Key] = ParsePrimitives(path, elem.Value, keyTypeMap)
53+
}
54+
return m
55+
case bson.A:
56+
result := make([]any, len(v))
57+
for i, item := range v {
58+
result[i] = ParsePrimitives(fmt.Sprintf("%s[%d]", key, i), item, keyTypeMap)
59+
}
60+
return result
61+
default:
62+
return v
63+
}
64+
}
65+
66+
func MarshalToBSONValue(key string, root any, keyTypeMap map[string]neosync_types.KeyType) any {
67+
if root == nil {
68+
return nil
69+
}
70+
71+
// Handle pointers
72+
val := reflect.ValueOf(root)
73+
for val.Kind() == reflect.Ptr {
74+
if val.IsNil() {
75+
return nil
76+
}
77+
val = val.Elem()
78+
}
79+
root = val.Interface()
80+
if typeStr, ok := keyTypeMap[key]; ok {
81+
switch typeStr {
82+
case neosync_types.Decimal128:
83+
_, ok := root.(primitive.Decimal128)
84+
if ok {
85+
return root
86+
}
87+
vStr, ok := root.(string)
88+
if ok {
89+
d, err := primitive.ParseDecimal128(vStr)
90+
if err == nil {
91+
return d
92+
}
93+
}
94+
vFloat, ok := root.(float64)
95+
if ok {
96+
d, err := primitive.ParseDecimal128(strconv.FormatFloat(vFloat, 'f', 4, 64))
97+
if err == nil {
98+
return d
99+
}
100+
}
101+
vBigFloat, ok := root.(big.Float)
102+
if ok {
103+
d, err := primitive.ParseDecimal128(vBigFloat.String())
104+
if err == nil {
105+
return d
106+
}
107+
}
108+
case neosync_types.Timestamp:
109+
_, ok := root.(primitive.Timestamp)
110+
if ok {
111+
return root
112+
}
113+
t, err := toUint32(root)
114+
if err == nil {
115+
return primitive.Timestamp{
116+
T: t,
117+
I: 1,
118+
}
119+
}
120+
121+
case neosync_types.ObjectID:
122+
_, ok := root.(primitive.ObjectID)
123+
if ok {
124+
return root
125+
}
126+
vStr, ok := root.(string)
127+
if ok {
128+
if objectID, err := primitive.ObjectIDFromHex(vStr); err == nil {
129+
return objectID
130+
}
131+
}
132+
}
133+
}
134+
135+
switch v := root.(type) {
136+
case map[string]any:
137+
doc := bson.D{}
138+
for k, v2 := range v {
139+
path := k
140+
if key != "" {
141+
path = fmt.Sprintf("%s.%s", key, k)
142+
}
143+
if path == "$set._id" {
144+
// don't set _id
145+
continue
146+
}
147+
doc = append(doc, bson.E{Key: k, Value: MarshalToBSONValue(path, v2, keyTypeMap)})
148+
}
149+
return doc
150+
151+
case []byte:
152+
return primitive.Binary{Data: v}
153+
154+
case []any:
155+
a := bson.A{}
156+
for i, v2 := range v {
157+
a = append(a, MarshalToBSONValue(fmt.Sprintf("%s[%d]", key, i), v2, keyTypeMap))
158+
}
159+
return a
160+
161+
case json.Number:
162+
n, err := v.Int64()
163+
if err == nil {
164+
return n
165+
}
166+
f, err := v.Float64()
167+
if err == nil {
168+
return f
169+
}
170+
return v.String()
171+
172+
case time.Time:
173+
return primitive.NewDateTimeFromTime(v)
174+
175+
case nil:
176+
return primitive.Null{}
177+
178+
default:
179+
return v
180+
}
181+
}
182+
183+
func MarshalJSONToBSONDocument(root any, keyTypeMap map[string]neosync_types.KeyType) bson.D {
184+
m, ok := root.(map[string]any)
185+
if !ok {
186+
return bson.D{}
187+
}
188+
189+
doc := bson.D{}
190+
for k, v := range m {
191+
doc = append(doc, bson.E{Key: k, Value: MarshalToBSONValue(k, v, keyTypeMap)})
192+
}
193+
return doc
194+
}
195+
196+
func toUint32(value any) (uint32, error) {
197+
switch v := value.(type) {
198+
case int:
199+
if v < 0 {
200+
return 0, errors.New("cannot convert negative int to uint32")
201+
}
202+
if v > math.MaxUint32 {
203+
return 0, errors.New("int value out of range for uint32")
204+
}
205+
return uint32(v), nil //nolint:gosec
206+
case int8:
207+
if v < 0 {
208+
return 0, errors.New("cannot convert negative int8 to uint32")
209+
}
210+
return uint32(v), nil
211+
case int16:
212+
if v < 0 {
213+
return 0, errors.New("cannot convert negative int16 to uint32")
214+
}
215+
return uint32(v), nil
216+
case int32:
217+
if v < 0 {
218+
return 0, errors.New("cannot convert negative int32 to uint32")
219+
}
220+
return uint32(v), nil
221+
case int64:
222+
if v < 0 || v > math.MaxUint32 {
223+
return 0, errors.New("int64 value out of range for uint32")
224+
}
225+
return uint32(v), nil //nolint:gosec
226+
case uint:
227+
if v > math.MaxUint32 {
228+
return 0, errors.New("uint value out of range for uint32")
229+
}
230+
return uint32(v), nil //nolint:gosec
231+
case uint8:
232+
return uint32(v), nil
233+
case uint16:
234+
return uint32(v), nil
235+
case uint32:
236+
return v, nil
237+
case uint64:
238+
if v > math.MaxUint32 {
239+
return 0, errors.New("uint64 value out of range for uint32")
240+
}
241+
return uint32(v), nil //nolint:gosec
242+
case float32:
243+
if v < 0 || v > math.MaxUint32 || float32(uint32(v)) != v {
244+
return 0, errors.New("float32 value out of range or not representable as uint32")
245+
}
246+
return uint32(v), nil
247+
case float64:
248+
if v < 0 || v > math.MaxUint32 || float64(uint32(v)) != v {
249+
return 0, errors.New("float64 value out of range or not representable as uint32")
250+
}
251+
return uint32(v), nil
252+
case string:
253+
num, err := strconv.ParseUint(v, 10, 32)
254+
if err != nil {
255+
return 0, fmt.Errorf("cannot convert string to uint32: %v", err)
256+
}
257+
return uint32(num), nil //nolint:gosec
258+
default:
259+
return 0, fmt.Errorf("unsupported type: %T", value)
260+
}
261+
}

0 commit comments

Comments
 (0)