Repository: prometheus/prometheus
File: util/annotations/annotations.go (Go)

Code before:
import (
	"errors"
	"fmt"

	"github.com/prometheus/common/model"

	"github.com/prometheus/prometheus/promql/parser/posrange"
)

// Annotations is a general wrapper for warnings and other information
// that is returned by the query API along with the results.
// Each individual annotation is modeled by a Go error.
// They are deduplicated based on the string returned by error.Error().
// The zero value is usable without further initialization, see New().
type Annotations map[string]annoErr

// New returns new Annotations ready to use. Note that the zero value of
// Annotations is also fully usable, but using this method is often more
// readable.
func New() *Annotations {
	return &Annotations{}
}

// Add adds an annotation (modeled as a Go error) in-place and returns the
// modified Annotations for convenience.
func (a *Annotations) Add(err annoErr) Annotations {
	if *a == nil {
		*a = Annotations{}
	}
	prevErr, exists := (*a)[err.Error()]
	if exists {
		err = err.merge(prevErr)
	}
	(*a)[err.Error()] = err
	return *a
}

// AddRaw is like Add, but a convenience wrapper for adding raw errors instead of annoErrs
// that have query and position information and possibly a custom merge function.
func (a *Annotations) AddRaw(err error) Annotations {
	return a.Add(&rawErr{Err: err})
}

// Merge adds the contents of the second annotation to the first, modifying
// the first in-place, and returns the merged first Annotation for convenience.
func (a *Annotations) Merge(aa Annotations) Annotations {
	if *a == nil {
		if aa == nil {
			return nil
		}
		*a = Annotations{}

Review comment (suggestion):
Again, it feels weird to expose a non-exported type in an exported method. The additional `AddRaw` also creates a lot of friction for the caller. Can we leave the old API intact (just with the one `Add(error)` method) and do the right thing behind the scenes with type assertions and `error.As`? (Does the latter work with interfaces?)

Code after:
import (
	"errors"
	"fmt"

	"github.com/prometheus/common/model"

	"github.com/prometheus/prometheus/promql/parser/posrange"
)

// Annotations is a general wrapper for warnings and other information
// that is returned by the query API along with the results.
// Each individual annotation is modeled by a Go error.
// They are deduplicated based on the string returned by error.Error().
// The zero value is usable without further initialization, see New().
type Annotations map[string]error

// New returns new Annotations ready to use. Note that the zero value of
// Annotations is also fully usable, but using this method is often more
// readable.
func New() *Annotations {
	return &Annotations{}
}

// Add adds an annotation (modeled as a Go error) in-place and returns the
// modified Annotations for convenience.
func (a *Annotations) Add(err error) Annotations {
	if *a == nil {
		*a = Annotations{}
	}
	if prevErr, exists := (*a)[err.Error()]; exists {
		var anErr annoError
		if errors.As(err, &anErr) {
			err = anErr.Merge(prevErr)
		}
	}
	(*a)[err.Error()] = err
	return *a
}

// Merge adds the contents of the second set of Annotations to the first, modifying
// the first in-place, and returns the merged first Annotations for convenience.
func (a *Annotations) Merge(aa Annotations) Annotations {
	if *a == nil {
		if aa == nil {
			return nil
		}
		*a = Annotations{}
	}
	for key, val := range aa {
		if prevVal, exists := (*a)[key]; exists {
			var anErr annoError