nilerror

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MPL-2.0 Imports: 7 Imported by: 0

README

nilerror Build Status Go Reference

Provides a River hook for detecting a common accidental Go problem where a nil struct value is wrapped in a non-nil interface value. This commonly causes trouble with the error interface, where an unintentional non-nil error is returned. For example:

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Will always return a non-nil error.
}

See https://go.dev/doc/faq#nil_error.

See example_hook_test.go for usage details.

Options

The hook supports these options:

hook := nilerror.NewHook(&HookConfig{
    Suppress: true,
})
  • Suppress: Causes the hook to suppress detected nil struct values wrapped in non-nil error interface values and produce warning logging instead.

Documentation

Overview

Package nilerror provides a River hook for detecting a common Go error where a nil struct value is wrapped in a non-nil interface value. This commonly causes trouble with the error interface, where an unintentional nil error is returned.

See: https://go.dev/doc/faq#nil_error.

The package must use reflection to work, and therefore necessitates some overhead from doing so. Its recommended use is to only the hook in test environments and detect non-nil nil errors via thorough testing, but it can only be used in production environments and configured to return an error on a detected problem or log a warning.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Hook

type Hook struct {
	baseservice.BaseService
	rivertype.Hook
	// contains filtered or unexported fields
}

Hook is a River hook that detects nil error structs accidentally wrapped in a non-nil error interface, and either returns an error or logs a warning.

See: https://go.dev/doc/faq#nil_error.

Example
package main

import (
	"context"
	"log/slog"
	"os"

	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/riverqueue/river"
	"github.com/riverqueue/river/riverdbtest"
	"github.com/riverqueue/river/riverdriver/riverpgxv5"
	"github.com/riverqueue/river/rivershared/riversharedtest"
	"github.com/riverqueue/river/rivershared/util/slogutil"
	"github.com/riverqueue/river/rivershared/util/testutil"
	"github.com/riverqueue/river/rivertype"
	"github.com/riverqueue/rivercontrib/nilerror"
)

type CustomError struct{}

func (*CustomError) Error() string {
	return "my custom error"
}

type CustomErrorArgs struct{}

func (CustomErrorArgs) Kind() string { return "custom_error" }

type CustomErrorWorker struct {
	river.WorkerDefaults[CustomErrorArgs]
}

func (w *CustomErrorWorker) Work(ctx context.Context, job *river.Job[CustomErrorArgs]) error {
	var customErr *CustomError // nil error, but non-nil when wrapped in an error interface
	return customErr
}

func main() {
	ctx := context.Background()

	dbPool, err := pgxpool.New(ctx, riversharedtest.TestDatabaseURL())
	if err != nil {
		panic(err)
	}
	defer dbPool.Close()

	workers := river.NewWorkers()
	river.AddWorker(workers, &CustomErrorWorker{})

	riverClient, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{
		Hooks: []rivertype.Hook{
			// Suppress option prevents errors in favor of warning logging when
			// a nil struct wrapped in a non-nil error interface is detected.
			nilerror.NewHook(&nilerror.HookConfig{Suppress: true}),

			// Alternatively, return an error and fail jobs instead.
			// nilerror.NewHook(nil),
		},
		Logger: slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelWarn, ReplaceAttr: slogutil.NoLevelTime})),
		Queues: map[string]river.QueueConfig{
			river.QueueDefault: {MaxWorkers: 100},
		},
		Schema:   riverdbtest.TestSchema(ctx, testutil.PanicTB(), riverpgxv5.New(dbPool), nil), // only necessary for the example test
		TestOnly: true,                                                                         // suitable only for use in tests; remove for live environments
		Workers:  workers,
	})
	if err != nil {
		panic(err)
	}

	// Out of example scope, but used to wait until a job is worked.
	subscribeChan, subscribeCancel := riverClient.Subscribe(river.EventKindJobCompleted)
	defer subscribeCancel()

	if _, err = riverClient.Insert(ctx, CustomErrorArgs{}, nil); err != nil {
		panic(err)
	}

	if err := riverClient.Start(ctx); err != nil {
		panic(err)
	}

	// Wait for jobs to complete. Only needed for purposes of the example test.
	riversharedtest.WaitOrTimeoutN(testutil.PanicTB(), subscribeChan, 1)

	if err := riverClient.Stop(ctx); err != nil {
		panic(err)
	}

}
Output:
msg="nilerror.Hook: Got non-nil error containing nil internal value (see: https://go.dev/doc/faq#nil_error); probably a bug: (*nilerror_test.CustomError)(<nil>)"

func NewHook

func NewHook(config *HookConfig) *Hook

NewHook initializes a new River nilerror hook.

config may be nil.

func (*Hook) WorkEnd

func (h *Hook) WorkEnd(ctx context.Context, _ *rivertype.JobRow, err error) error

type HookConfig

type HookConfig struct {
	// Suppress causes the hook to suppress detected nil struct values wrapped
	// in non-nil error interface values and produce warning logging instead.
	Suppress bool
}

HookConfig is configuration for the nilerror hook.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL