Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions event/monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type CommandSucceededEvent struct {
type CommandFailedEvent struct {
CommandFinishedEvent
Failure error
Codes []int32
}

// CommandMonitor represents a monitor that is triggered for different events.
Expand Down
33 changes: 32 additions & 1 deletion internal/integration/clam_prose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (

"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/assert"
"go.mongodb.org/mongo-driver/v2/internal/failpoint"
"go.mongodb.org/mongo-driver/v2/internal/integration/mtest"
"go.mongodb.org/mongo-driver/v2/internal/integtest"
"go.mongodb.org/mongo-driver/v2/internal/logger"
"go.mongodb.org/mongo-driver/v2/internal/require"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
Expand Down Expand Up @@ -198,7 +200,6 @@ func clamMultiByteTruncLogs(mt *mtest.T) []truncValidator {

// Insert started.
validators[0] = newTruncValidator(mt, cmd, func(cmd string) error {

// Remove the suffix from the command string.
cmd = cmd[:len(cmd)-len(logger.TruncationSuffix)]

Expand Down Expand Up @@ -396,3 +397,33 @@ func TestCommandLoggingAndMonitoringProse(t *testing.T) {
})
}
}

func TestCommandFailedEvent_Codes(t *testing.T) {
clientOpts := options.Client().SetRetryWrites(false)

mtOpts := mtest.NewOptions().MinServerVersion("8.0").ClientType(mtest.Pinned).ClientOptions(clientOpts)
mt := mtest.New(t, mtOpts)

mt.Run("Top level code", func(mt *mtest.T) {
mt.ResetClient(options.Client().SetRetryWrites(false))

mt.SetFailPoint(failpoint.FailPoint{
ConfigureFailPoint: "failCommand",
Mode: failpoint.Mode{
Times: 1,
},
Data: failpoint.Data{
FailCommands: []string{"insert"},
ErrorCode: 1,
},
})

_, err := mt.Coll.InsertOne(context.Background(), struct{ X int }{X: 1})
require.Error(mt, err, "expected InsertOne error, got nil")

failedEvent := mt.GetFailedEvent()

require.NotNil(mt, failedEvent, "expected CommandFailedEvent, got nil")
require.Equal(mt, []int32{1}, failedEvent.Codes, "expected Codes to be [1], got %+v", failedEvent.Codes)
})
}
10 changes: 6 additions & 4 deletions mongo/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,12 @@ func hasErrorCode(srvErr ServerError, code int) bool {
return false
}

var _ ServerError = CommandError{}
var _ ServerError = WriteError{}
var _ ServerError = WriteException{}
var _ ServerError = BulkWriteException{}
var (
_ ServerError = CommandError{}
_ ServerError = WriteError{}
_ ServerError = WriteException{}
_ ServerError = BulkWriteException{}
)

var _ error = ClientBulkWriteException{}

Expand Down
18 changes: 18 additions & 0 deletions x/mongo/driver/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,21 @@ func ExtractErrorFromServerResponse(doc bsoncore.Document) error {

return nil
}

// errorCodes extracts error codes from a driver.Error representing an ok:0
// command failure. Since ok:0 indicates the command failed before processing
// any operations, only a single error code is returned. Returns nil if the
// error is not a driver.Error (e.g., network errors, WriteCommandError for
// ok:1 responses).
//
// This function may be updated in the future to extract multiple error codes in
// an ok:1 scenario. In such cases, there can be multiple error codes via
// WriteErrors which represent individual operation failures within the context
// of a write command.
func errorCodes(err error) []int32 {
if driversErr, ok := err.(Error); ok {
return []int32{driversErr.Code}
}

Comment on lines +581 to +584
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type assertion should use the modern syntax with type parameter. Change err.(Error) to use a type switch or keep the current form but consider that direct type assertion without pointer check may not catch *Error types. Verify if both Error and *Error need to be handled.

Suggested change
if driversErr, ok := err.(Error); ok {
return []int32{driversErr.Code}
}
switch e := err.(type) {
case Error:
return []int32{e.Code}
case *Error:
if e != nil {
return []int32{e.Code}
}
}

Copilot uses AI. Check for mistakes.
return nil
}
2 changes: 1 addition & 1 deletion x/mongo/driver/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,6 @@ func (op Operation) Execute(ctx context.Context) error {
var moreToCome bool
var startedInfo startedInformation
*wm, moreToCome, startedInfo, err = op.createWireMessage(ctx, maxTimeMS, (*wm)[:0], desc, conn, requestID)

if err != nil {
return err
}
Expand Down Expand Up @@ -2231,6 +2230,7 @@ func (op Operation) publishFinishedEvent(ctx context.Context, info finishedInfor

failedEvent := &event.CommandFailedEvent{
Failure: info.cmdErr,
Codes: errorCodes(info.cmdErr),
CommandFinishedEvent: finished,
}
op.CommandMonitor.Failed(ctx, failedEvent)
Expand Down
Loading