package simon import ( "context" "errors" "fmt" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) type Map map[string]any var ( ErrEmptyCollection = fmt.Errorf("collection has no documents") ) type Filters struct { id func(bson.ObjectID) Map } var filters Filters = Filters{ id: idFilter, } func idFilter(id bson.ObjectID) Map { return Map{"_id": id} } func Update( ctx context.Context, collection *mongo.Collection, filter any, newValues any, opts ...options.Lister[options.FindOneAndUpdateOptions], ) error { result := collection.FindOneAndUpdate(ctx, filter, Map{"$set": newValues}, opts...) err := result.Err() return err } func UpdateAndReturn[T any]( ctx context.Context, collection *mongo.Collection, filter any, newValues any, opts ...options.Lister[options.FindOneAndUpdateOptions], ) (*T, error) { var tmp T result := collection.FindOneAndUpdate(ctx, filter, Map{"$set": newValues}, opts...) err := result.Err() if err != nil { return nil, err } err = result.Decode(&tmp) if err != nil { return nil, err } return &tmp, err } func UpdateMany( ctx context.Context, collection *mongo.Collection, filter any, newValues any, ) error { _, err := collection.UpdateMany( ctx, filter, Map{"$set": newValues}, ) return err } func InsertOneAndRetrieve[T any]( ctx context.Context, collection *mongo.Collection, v any, ) (*T, error) { id, err := InsertOne(ctx, collection, v) if err != nil { return nil, err } return FindOne[T](ctx, collection, Map{"_id": id}) } func InsertOne(ctx context.Context, collection *mongo.Collection, v any) (bson.ObjectID, error) { var noID bson.ObjectID result, err := collection.InsertOne(ctx, v) if err != nil { return noID, err } id, ok := result.InsertedID.(bson.ObjectID) if !ok { return noID, fmt.Errorf("type assertion for id,ok := result.InsertedID.(bson.ObjectID) failed: InsertedID: %v", result.InsertedID) } return id, nil } func InsertOneAndReturn[T any](ctx context.Context, collection *mongo.Collection, v any) (*T, error) { result, err := collection.InsertOne(ctx, v) if err != nil { return nil, err } id, ok := result.InsertedID.(bson.ObjectID) if !ok { return nil, fmt.Errorf("type assertion for id,ok := result.InsertedID.(bson.ObjectID) failed: InsertedID: %v", result.InsertedID) } return FindOne[T](ctx, collection, filters.id(id)) } func FindOne[T any](ctx context.Context, collection *mongo.Collection, filter any, opts ...options.Lister[options.FindOneOptions]) (*T, error) { var tmp T result := collection.FindOne(ctx, filter) err := result.Err() if err != nil { return nil, err } result.Decode(&tmp) return &tmp, nil } func Append[T any](ctx context.Context, collection *mongo.Collection, filter any, value any) (*T, error) { var tmp T result := collection.FindOne(ctx, filter) err := result.Err() if err != nil { return nil, err } result.Decode(&tmp) return &tmp, nil } func List[T any](ctx context.Context, collection *mongo.Collection, filter any) ([]*T, error) { var list []*T cursor, err := collection.Find(ctx, filter) if err != nil { return nil, err } for cursor.Next(ctx) { var tmp T err := cursor.Decode(&tmp) if err != nil { return nil, err } list = append(list, &tmp) } err = cursor.Err() if err != nil { return nil, err } return list, nil } func Upsert[T any](ctx context.Context, collection *mongo.Collection, params T, filter any) (*T, error) { item, err := FindOne[T](ctx, collection, filters) if err != nil { switch { case errors.Is(err, ErrEmptyCollection): return InsertOneAndRetrieve[T](ctx, collection, params) default: return nil, err } } item, err = UpdateAndReturn[T]( ctx, collection, filter, //nolint params, options.FindOneAndUpdate().SetReturnDocument(options.After), ) return item, err }