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 }