Files
go-omni/response/errors.go

85 lines
2.8 KiB
Go

package response
import (
"fmt"
"reflect"
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
)
// ErrorRegistryProvider attaches an immutable error catalog to a route policy.
// Contexts and buffered clones use it before encoding or running middleware.
type ErrorRegistryProvider interface{ ErrorRegistry() *apierrors.Registry }
type errorEncoder struct {
next Encoder
registry *apierrors.Registry
}
func (e *errorEncoder) ErrorRegistry() *apierrors.Registry { return e.registry }
func (e *errorEncoder) Encode(r Result) (Payload, error) {
if r.Err != nil {
err := e.registry.Resolve(r.Err)
r.Status, r.Code, r.Message, r.BusinessCode, r.Failed = err.Status, err.Code, err.Message, err.BusinessCode, true
}
return e.next.Encode(r)
}
// WithErrors binds a catalog to an encoder without imposing a response shape.
// Built-in templates are checked for incompatible code types and conflicting
// legacy mappings. Custom encoders remain responsible for their wire contract.
func WithErrors(catalog apierrors.Catalog, next Encoder) (Encoder, error) {
registry, err := apierrors.NewRegistry(catalog)
if err != nil {
return nil, err
}
if next == nil {
next = Default()
}
if provider, ok := next.(ErrorRegistryProvider); ok && provider.ErrorRegistry() != nil {
if len(catalog.Definitions) == 0 {
return next, nil
}
if reflect.DeepEqual(registry.Catalog(), provider.ErrorRegistry().Catalog()) {
return next, nil
}
return nil, fmt.Errorf("response: cannot stack different error catalogs; choose one policy per scope")
}
if checker, ok := next.(interface{ ValidateErrors(apierrors.Catalog) error }); ok {
if err := checker.ValidateErrors(registry.Catalog()); err != nil {
return nil, err
}
}
return &errorEncoder{next: next, registry: registry}, nil
}
func MustWithErrors(catalog apierrors.Catalog, next Encoder) Encoder {
e, err := WithErrors(catalog, next)
if err != nil {
panic(err)
}
return e
}
func (f *Formatter) ValidateErrors(catalog apierrors.Catalog) error {
for key, d := range catalog.Definitions {
if err := f.validateError(Result{Code: key, Message: d.Message, BusinessCode: d.Code, Failed: true}); err != nil {
return err
}
}
return nil
}
func (f *Formatter) validateError(r Result) error {
if !r.IsError() || r.BusinessCode == nil {
return nil
}
normalized, err := normalizeCode(r.BusinessCode, f.config.CodeType)
if err != nil {
return fmt.Errorf("response: catalog %s code does not match codeType %s: %w", r.Code, f.config.CodeType, err)
}
if code, ok := f.config.Codes[r.Code]; ok && !reflect.DeepEqual(code, normalized) {
return fmt.Errorf("response: conflicting catalog/code mapping for %s", r.Code)
}
if msg, ok := f.config.Messages[r.Code]; ok && msg != r.Message {
return fmt.Errorf("response: conflicting catalog/message mapping for %s", r.Code)
}
return nil
}