Skip to content

Commit b8e668f

Browse files
finder: Add Method FindOneAndUpdate and refactor methods a and preActionHandler and postActionHandler (#50)
- Change the opTypes parameter type of functions preActionHandler and postActionHandler to slice. - Add Method FindOneAndUpdate.
1 parent 54cc582 commit b8e668f

File tree

5 files changed

+335
-18
lines changed

5 files changed

+335
-18
lines changed

finder/finder.go

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var _ iFinder[any] = (*Finder[any])(nil)
4343
type Finder[T any] struct {
4444
collection *mongo.Collection
4545
filter any
46+
updates any
4647
beforeHooks []beforeHookFn
4748
afterHooks []afterHookFn[T]
4849
}
@@ -66,32 +67,41 @@ func (f *Finder[T]) Filter(filter any) *Finder[T] {
6667
return f
6768
}
6869

69-
func (f *Finder[T]) preActionHandler(ctx context.Context, globalOpContext *operation.OpContext, opContext *OpContext, opType operation.OpType) error {
70-
err := callback.GetCallback().Execute(ctx, globalOpContext, opType)
71-
if err != nil {
72-
return err
70+
func (f *Finder[T]) Updates(update any) *Finder[T] {
71+
f.updates = update
72+
return f
73+
}
74+
75+
func (f *Finder[T]) preActionHandler(ctx context.Context, globalOpContext *operation.OpContext, opContext *OpContext, opTypes ...operation.OpType) (err error) {
76+
for _, opType := range opTypes {
77+
err = callback.GetCallback().Execute(ctx, globalOpContext, opType)
78+
if err != nil {
79+
return
80+
}
7381
}
7482
for _, beforeHook := range f.beforeHooks {
7583
err = beforeHook(ctx, opContext)
7684
if err != nil {
7785
return err
7886
}
7987
}
80-
return nil
88+
return
8189
}
8290

83-
func (f *Finder[T]) postActionHandler(ctx context.Context, globalOpContext *operation.OpContext, opContext *AfterOpContext[T], opType operation.OpType) error {
84-
err := callback.GetCallback().Execute(ctx, globalOpContext, opType)
85-
if err != nil {
86-
return err
91+
func (f *Finder[T]) postActionHandler(ctx context.Context, globalOpContext *operation.OpContext, opContext *AfterOpContext[T], opTypes ...operation.OpType) (err error) {
92+
for _, opType := range opTypes {
93+
err = callback.GetCallback().Execute(ctx, globalOpContext, opType)
94+
if err != nil {
95+
return
96+
}
8797
}
8898
for _, afterHook := range f.afterHooks {
8999
err = afterHook(ctx, opContext)
90100
if err != nil {
91-
return err
101+
return
92102
}
93103
}
94-
return nil
104+
return
95105
}
96106

97107
func (f *Finder[T]) FindOne(ctx context.Context, opts ...*options.FindOneOptions) (*T, error) {
@@ -171,3 +181,25 @@ func (f *Finder[T]) DistinctWithParse(ctx context.Context, fieldName string, res
171181
}
172182
return nil
173183
}
184+
185+
func (f *Finder[T]) FindOneAndUpdate(ctx context.Context, opts ...*options.FindOneAndUpdateOptions) (*T, error) {
186+
t := new(T)
187+
188+
globalOpContext := operation.NewOpContext(f.collection, operation.WithDoc(t), operation.WithFilter(f.filter), operation.WithUpdate(f.updates))
189+
err := f.preActionHandler(ctx, globalOpContext, NewOpContext(f.collection, f.filter, WithUpdates(f.updates)), operation.OpTypeBeforeFind, operation.OpTypeBeforeUpdate)
190+
if err != nil {
191+
return nil, err
192+
}
193+
194+
err = f.collection.FindOneAndUpdate(ctx, f.filter, f.updates, opts...).Decode(t)
195+
if err != nil {
196+
return nil, err
197+
}
198+
199+
err = f.postActionHandler(ctx, globalOpContext, NewAfterOpContext[T](NewOpContext(f.collection, f.filter, WithUpdates(f.updates)), WithDoc(t)), operation.OpTypeAfterFind, operation.OpTypeAfterUpdate)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
return t, nil
205+
}

finder/finder_e2e_test.go

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"fmt"
2323
"testing"
2424

25+
"github.com/chenmingyong0423/go-mongox/builder/update"
26+
2527
"github.com/chenmingyong0423/go-mongox/internal/pkg/utils"
2628

2729
"github.com/chenmingyong0423/go-mongox/callback"
@@ -1091,3 +1093,281 @@ func TestFinder_e2e_DistinctWithParse(t *testing.T) {
10911093
require.Error(t, err)
10921094
})
10931095
}
1096+
1097+
func TestFinder_e2e_FindOneAndUpdate(t *testing.T) {
1098+
collection := getCollection(t)
1099+
finder := NewFinder[TestUser](collection)
1100+
1101+
type globalHook struct {
1102+
opType operation.OpType
1103+
name string
1104+
fn callback.CbFn
1105+
}
1106+
testCases := []struct {
1107+
name string
1108+
before func(ctx context.Context, t *testing.T)
1109+
after func(ctx context.Context, t *testing.T)
1110+
1111+
filter any
1112+
updates any
1113+
opts []*options.FindOneAndUpdateOptions
1114+
globalHook []globalHook
1115+
beforeHook []beforeHookFn
1116+
afterHook []afterHookFn[TestUser]
1117+
1118+
ctx context.Context
1119+
want *TestUser
1120+
wantErr error
1121+
}{
1122+
{
1123+
name: "nil document",
1124+
before: func(ctx context.Context, t *testing.T) {
1125+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1126+
Name: "chenmingyong",
1127+
Age: 24,
1128+
})
1129+
require.NoError(t, err)
1130+
require.NotNil(t, insertOneResult.InsertedID)
1131+
},
1132+
after: func(ctx context.Context, t *testing.T) {
1133+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1134+
require.NoError(t, err)
1135+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1136+
1137+
finder.filter = bson.D{}
1138+
},
1139+
filter: query.Eq("name", "burt"),
1140+
wantErr: mongo.ErrNilDocument,
1141+
},
1142+
{
1143+
name: "find by name and update age",
1144+
before: func(ctx context.Context, t *testing.T) {
1145+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1146+
Name: "chenmingyong",
1147+
Age: 18,
1148+
})
1149+
require.NoError(t, err)
1150+
require.NotNil(t, insertOneResult.InsertedID)
1151+
},
1152+
after: func(ctx context.Context, t *testing.T) {
1153+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1154+
require.NoError(t, err)
1155+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1156+
1157+
finder.filter = bson.D{}
1158+
},
1159+
filter: query.Eq("name", "chenmingyong"),
1160+
updates: update.Set("age", 24),
1161+
opts: []*options.FindOneAndUpdateOptions{options.FindOneAndUpdate().SetReturnDocument(options.After)},
1162+
want: &TestUser{
1163+
Name: "chenmingyong",
1164+
Age: 24,
1165+
},
1166+
},
1167+
{
1168+
name: "global before hook error",
1169+
before: func(ctx context.Context, t *testing.T) {},
1170+
after: func(ctx context.Context, t *testing.T) {},
1171+
filter: query.Eq("name", "Mingyong Chen"),
1172+
globalHook: []globalHook{
1173+
{
1174+
opType: operation.OpTypeBeforeFind,
1175+
name: "before hook error",
1176+
fn: func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error {
1177+
return errors.New("global before hook error")
1178+
},
1179+
},
1180+
},
1181+
wantErr: errors.New("global before hook error"),
1182+
},
1183+
{
1184+
name: "global after hook error",
1185+
before: func(ctx context.Context, t *testing.T) {
1186+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1187+
Name: "chenmingyong",
1188+
Age: 18,
1189+
})
1190+
require.NoError(t, err)
1191+
require.NotNil(t, insertOneResult.InsertedID)
1192+
},
1193+
after: func(ctx context.Context, t *testing.T) {
1194+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1195+
require.NoError(t, err)
1196+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1197+
1198+
finder.filter = bson.D{}
1199+
},
1200+
filter: query.Eq("name", "chenmingyong"),
1201+
updates: update.Set("age", 24),
1202+
opts: []*options.FindOneAndUpdateOptions{options.FindOneAndUpdate().SetReturnDocument(options.After)},
1203+
globalHook: []globalHook{
1204+
{
1205+
opType: operation.OpTypeAfterFind,
1206+
name: "after hook error",
1207+
fn: func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error {
1208+
return errors.New("global after hook error")
1209+
},
1210+
},
1211+
},
1212+
wantErr: errors.New("global after hook error"),
1213+
},
1214+
{
1215+
name: "global before and after hook",
1216+
before: func(ctx context.Context, t *testing.T) {
1217+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1218+
Name: "chenmingyong",
1219+
Age: 18,
1220+
})
1221+
require.NoError(t, err)
1222+
require.NotNil(t, insertOneResult.InsertedID)
1223+
},
1224+
after: func(ctx context.Context, t *testing.T) {
1225+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1226+
require.NoError(t, err)
1227+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1228+
1229+
finder.filter = bson.D{}
1230+
},
1231+
filter: query.Eq("name", "chenmingyong"),
1232+
updates: update.Set("age", 24),
1233+
opts: []*options.FindOneAndUpdateOptions{options.FindOneAndUpdate().SetReturnDocument(options.After)},
1234+
globalHook: []globalHook{
1235+
{
1236+
opType: operation.OpTypeBeforeFind,
1237+
name: "before hook",
1238+
fn: func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error {
1239+
if opCtx.Filter.(bson.D)[0].Key != "name" || opCtx.Filter.(bson.D)[0].Value.(bson.D)[0].Value != "chenmingyong" {
1240+
return errors.New("filter error")
1241+
}
1242+
if opCtx.Updates.(bson.D)[0].Value.(bson.D)[0].Key != "age" || opCtx.Updates.(bson.D)[0].Value.(bson.D)[0].Value != 24 {
1243+
return errors.New("updates error")
1244+
}
1245+
return nil
1246+
},
1247+
},
1248+
{
1249+
opType: operation.OpTypeAfterFind,
1250+
name: "after hook",
1251+
fn: func(ctx context.Context, opCtx *operation.OpContext, opts ...any) error {
1252+
user := opCtx.Doc.(*TestUser)
1253+
if user.Name != "chenmingyong" || user.Age != 24 {
1254+
return errors.New("result error")
1255+
}
1256+
return nil
1257+
},
1258+
},
1259+
},
1260+
want: &TestUser{
1261+
Name: "chenmingyong",
1262+
Age: 24,
1263+
},
1264+
},
1265+
{
1266+
name: "before hook error",
1267+
before: func(ctx context.Context, t *testing.T) {},
1268+
after: func(ctx context.Context, t *testing.T) {},
1269+
filter: query.Eq("name", "chenmingyong"),
1270+
beforeHook: []beforeHookFn{
1271+
func(ctx context.Context, opCtx *OpContext, opts ...any) error {
1272+
return errors.New("before hook error")
1273+
},
1274+
},
1275+
wantErr: errors.New("before hook error"),
1276+
},
1277+
{
1278+
name: "after hook error",
1279+
before: func(ctx context.Context, t *testing.T) {
1280+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1281+
Name: "chenmingyong",
1282+
Age: 18,
1283+
})
1284+
require.NoError(t, err)
1285+
require.NotNil(t, insertOneResult.InsertedID)
1286+
},
1287+
after: func(ctx context.Context, t *testing.T) {
1288+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1289+
require.NoError(t, err)
1290+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1291+
1292+
finder.filter = bson.D{}
1293+
},
1294+
filter: query.Eq("name", "chenmingyong"),
1295+
updates: update.Set("age", 24),
1296+
opts: []*options.FindOneAndUpdateOptions{options.FindOneAndUpdate().SetReturnDocument(options.After)},
1297+
afterHook: []afterHookFn[TestUser]{
1298+
func(ctx context.Context, opCtx *AfterOpContext[TestUser], opts ...any) error {
1299+
return errors.New("after hook error")
1300+
},
1301+
},
1302+
wantErr: errors.New("after hook error"),
1303+
},
1304+
{
1305+
name: "before and after hook",
1306+
before: func(ctx context.Context, t *testing.T) {
1307+
insertOneResult, err := collection.InsertOne(ctx, &TestUser{
1308+
Name: "chenmingyong",
1309+
Age: 18,
1310+
})
1311+
require.NoError(t, err)
1312+
require.NotNil(t, insertOneResult.InsertedID)
1313+
},
1314+
after: func(ctx context.Context, t *testing.T) {
1315+
deleteOneResult, err := collection.DeleteOne(ctx, query.Eq("name", "chenmingyong"))
1316+
require.NoError(t, err)
1317+
require.Equal(t, int64(1), deleteOneResult.DeletedCount)
1318+
1319+
finder.filter = bson.D{}
1320+
},
1321+
filter: query.Eq("name", "chenmingyong"),
1322+
updates: update.Set("age", 24),
1323+
opts: []*options.FindOneAndUpdateOptions{options.FindOneAndUpdate().SetReturnDocument(options.After)},
1324+
beforeHook: []beforeHookFn{
1325+
func(ctx context.Context, opCtx *OpContext, opts ...any) error {
1326+
if opCtx.Filter.(bson.D)[0].Key != "name" || opCtx.Filter.(bson.D)[0].Value.(bson.D)[0].Value != "chenmingyong" {
1327+
return errors.New("filter error")
1328+
}
1329+
if opCtx.Updates.(bson.D)[0].Value.(bson.D)[0].Key != "age" || opCtx.Updates.(bson.D)[0].Value.(bson.D)[0].Value != 24 {
1330+
return errors.New("updates error")
1331+
}
1332+
return nil
1333+
},
1334+
},
1335+
afterHook: []afterHookFn[TestUser]{
1336+
func(ctx context.Context, opCtx *AfterOpContext[TestUser], opts ...any) error {
1337+
user := opCtx.Doc
1338+
if user.Name != "chenmingyong" || user.Age != 24 {
1339+
return errors.New("after error")
1340+
}
1341+
return nil
1342+
},
1343+
},
1344+
want: &TestUser{
1345+
Name: "chenmingyong",
1346+
Age: 24,
1347+
},
1348+
},
1349+
}
1350+
1351+
for _, tc := range testCases {
1352+
t.Run(tc.name, func(t *testing.T) {
1353+
tc.before(tc.ctx, t)
1354+
for _, hook := range tc.globalHook {
1355+
callback.GetCallback().Register(hook.opType, hook.name, hook.fn)
1356+
}
1357+
user, err := finder.RegisterBeforeHooks(tc.beforeHook...).
1358+
RegisterAfterHooks(tc.afterHook...).Filter(tc.filter).Updates(tc.updates).
1359+
FindOneAndUpdate(tc.ctx, tc.opts...)
1360+
tc.after(tc.ctx, t)
1361+
require.Equal(t, tc.wantErr, err)
1362+
if err == nil {
1363+
tc.want.ID = user.ID
1364+
require.Equal(t, tc.want, user)
1365+
}
1366+
for _, hook := range tc.globalHook {
1367+
callback.GetCallback().Remove(hook.opType, hook.name)
1368+
}
1369+
finder.beforeHooks = nil
1370+
finder.afterHooks = nil
1371+
})
1372+
}
1373+
}

finder/opt_after_op_context_gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Generated by optioner -type AfterOpContext; DO NOT EDIT
1+
// Generated by [optioner] command-line tool; DO NOT EDIT
22
// If you have any questions, please create issues and submit contributions at:
33
// https://github.com/chenmingyong0423/go-optioner
44

0 commit comments

Comments
 (0)