Skip to content

Error Types

All Den errors are typed sentinel values that support errors.Is() and errors.As() for reliable error handling.

Import: github.com/oliverandrich/den


Error Reference

Error Description When Returned
ErrNotFound Document lookup yielded no result FindByID, First, UpdateOne, Refresh, Save (update branch), Delete when the document does not exist; also surfaced by Iter on a dangling link reached via WithFetchLinks
ErrMultipleMatches A single-document lookup matched more than one row QuerySet.UpdateOne / UpsertOne / GetOrCreate when conditions match more than one document. The conditions must identify the document uniquely
ErrDuplicate Unique index constraint violated Save when a document with the same unique field value already exists
ErrRevisionConflict Optimistic concurrency check failed Save (update branch) when the document's _rev does not match the stored revision (another process modified it)
ErrNotRegistered Operating on an unregistered document type Any CRUD or query operation on a type not passed to den.Register()
ErrValidation Validation hook returned an error Save when the document's Validate() method or struct tag validation fails
ErrTransactionFailed Transaction could not be committed RunInTransaction when the commit fails
ErrNoSnapshot No stored snapshot to revert to Revert when the document was never loaded from the database or does not embed Tracked
ErrMigrationFailed A migration function returned an error Registry.Up, Registry.UpOne when a migration fails; wraps the original error with the migration version
ErrLocked Row is locked by another transaction LockByID(NoWait()) or QuerySet.ForUpdate(NoWait()) terminals when another transaction holds the row lock (PostgreSQL only; SQLite never returns this)
ErrUnsupportedScheme DSN scheme has no registered backend OpenURL when the scheme prefix (e.g. mongodb://) does not match any backend imported with _ "github.com/oliverandrich/den/backend/…"
ErrDeadlock PostgreSQL reported a deadlock between transactions Any operation on PostgreSQL when the server cancels the query with SQLSTATE 40P01. Callers can errors.Is(err, den.ErrDeadlock) and retry the transaction. SQLite never returns this
ErrSerialization Serializable or repeatable-read transaction could not be serialized PostgreSQL SQLSTATE 40001. Becomes relevant once callers opt into stricter isolation levels; standard Den operations using the default isolation level rarely see this
ErrFTSNotSupported Backend does not implement full-text search QuerySet.Search when the active backend does not provide an FTSProvider implementation
ErrLockRequiresTransaction QuerySet.ForUpdate used on a *DB scope Terminal methods (All, First, Count, …) on a QuerySet where ForUpdate was set but the scope is not a *Tx. Row locking is meaningless outside a transaction
ErrIncompatiblePagination Cursor and offset pagination combined Any terminal method on a QuerySet where After/Before is set together with Skip. Pick one pagination style per chain

Typed error values

*DanglingLinkError — struct (not sentinel) returned by batched link resolution when a Link[T] ID does not resolve to any row. Wraps ErrNotFound (so errors.Is(err, den.ErrNotFound) matches) and exposes the Collection and ID fields for diagnostics:

var dle *den.DanglingLinkError
if errors.As(err, &dle) {
    log.Printf("dangling link in %s: %s", dle.Collection, dle.ID)
}

Usage with errors.Is

product, err := den.FindByID[Product](ctx, db, "nonexistent-id")
if errors.Is(err, den.ErrNotFound) {
    // handle missing document
    log.Println("product not found")
}
err := den.Save(ctx, db, &product)
if errors.Is(err, den.ErrDuplicate) {
    // handle unique constraint violation
    log.Println("a product with that SKU already exists")
}
err := den.Save(ctx, db, &product)
if errors.Is(err, den.ErrRevisionConflict) {
    // re-read and retry, or inform the user
    log.Println("document was modified by another process")
}

Validation Errors

When using the validate sub-package (github.com/oliverandrich/den/validate), validation errors can be unwrapped to access per-field details:

err := den.Save(ctx, db, &user)
if errors.Is(err, den.ErrValidation) {
    var ve *validate.Errors
    if errors.As(err, &ve) {
        for _, fieldErr := range ve.Fields {
            log.Printf("field %s failed %q (value=%v, param=%q)",
                fieldErr.Field, fieldErr.Tag, fieldErr.Value, fieldErr.Param)
        }
    }
}

Error Wrapping

All Den errors can be wrapped with additional context. The original sentinel remains matchable via errors.Is():

// Inside Den, errors are wrapped like this:
return fmt.Errorf("insert into %s: %w", collection, den.ErrDuplicate)

// Your code can still match:
errors.Is(err, den.ErrDuplicate) // true