85 lines
2.8 KiB
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
|
|
}
|