diff --git a/factory.go b/factory.go index 0019274..422f322 100644 --- a/factory.go +++ b/factory.go @@ -14,6 +14,12 @@ type Factory[T any] interface { New(vs ...T) Falta } +// ExtendableFactory is an error factory that can be extended. +type ExtendableFactory[T any] interface { + Factory[T] + Extend(f Factory[T]) ExtendableFactory[T] +} + // Falta is an error returned by the Factory type Falta struct { errFmt string @@ -103,20 +109,20 @@ func New[T any](errFmt string) Factory[T] { return newTmplFalta[T](errFmt) } -// NewM returns a new Falta instance using a template that expects a falta.M (i.e., map[string]any). This is a -// convenience function for calling falta.New[falta.M](...) -func NewM(errFmt string) Factory[M] { - return New[M](errFmt) +// M is a convenience type for using Falta instances with maps. +type M map[string]any + +// NewM returns a new ExtendableFactory instance using a template that expects a falta.M (i.e., map[string]any). +// This is a convenience function for calling falta.New[falta.M](...) +func NewM(errFmt string) ExtendableFactory[M] { + return newTmplFalta[M](errFmt) } // Newf creates a new Falta instance that will construct errors using the printf format string provided. -func Newf(errFmt string) Factory[any] { +func Newf(errFmt string) ExtendableFactory[any] { return newFmtFactory(errFmt) } -// M is a convenience type for using Falta instances with maps. -type M map[string]any - type tmplFalta[T any] struct { errFmt string tmpl *template.Template @@ -139,12 +145,22 @@ func (f tmplFalta[T]) New(vs ...T) Falta { builder := new(strings.Builder) if err := f.tmpl.Execute(builder, vs[0]); err != nil { - panic(err) + panic(fmt.Errorf("falta: cannot execute template: %w", err)) } return Falta{errFmt: f.errFmt, error: fmt.Errorf(builder.String())} } +func (f tmplFalta[T]) Extend(other Factory[T]) ExtendableFactory[T] { + v, ok := other.(tmplFalta[T]) + + if !ok { + panic(fmt.Errorf("falta: tmpl factories can only be extended by other tmpl factories with the same type")) + } + + return newTmplFalta[T](f.errFmt + " " + v.errFmt) +} + func (f tmplFalta[T]) Error() string { return f.errFmt } @@ -175,6 +191,16 @@ func (f fmtFalta) New(vs ...any) Falta { return Falta{errFmt: f.errFmt, error: fmt.Errorf(f.errFmt, vs...)} } +func (f fmtFalta) Extend(other Factory[any]) ExtendableFactory[any] { + v, ok := other.(fmtFalta) + + if !ok { + panic(fmt.Errorf("falta: fmt factories can only be extended by other fmt factories")) + } + + return newFmtFactory(f.errFmt + " " + v.errFmt) +} + func (f fmtFalta) Error() string { return f.errFmt } diff --git a/factory_test.go b/factory_test.go index f593feb..aee399f 100644 --- a/factory_test.go +++ b/factory_test.go @@ -123,12 +123,41 @@ func TestNewError(t *testing.T) { } func TestNewM(t *testing.T) { - f := falta.NewM("falta test: [code={{.code}}] test error with message: {{.message}}") + f := falta.NewM("falta test: [code={{.code}}] test error with message '{{.message}}'") + err := f.New(falta.M{ "code": 503, "message": "Bad Gateway", }) - expectedErr := fmt.Errorf("falta test: [code=503] test error with message: Bad Gateway") + expectedErr := fmt.Errorf("falta test: [code=503] test error with message 'Bad Gateway'") assert.ErrorIs(t, err, expectedErr) } + +func TestExtendableFactory(t *testing.T) { + ErrCallFailed := falta.NewM("falta test: [code={{.code}}] test error with message '{{.message}}'") + ErrCallFailedWithReason := ErrCallFailed.Extend(falta.NewM("because {{.reason}}")) + + as := assert.New(t) + + { + err := ErrCallFailed.New(falta.M{ + "code": 503, + "message": "Bad Gateway", + }) + + expectedErr := fmt.Errorf("falta test: [code=503] test error with message 'Bad Gateway'") + as.ErrorIs(err, expectedErr) + } + + { + err := ErrCallFailedWithReason.New(falta.M{ + "code": 503, + "message": "Bad Gateway", + "reason": "server is down", + }) + + expectedErr := fmt.Errorf("falta test: [code=503] test error with message 'Bad Gateway' because server is down") + as.ErrorIs(err, expectedErr) + } +}