diff --git a/modules/client/client.go b/modules/client/client.go index 2cfa1e0..6d67cd7 100644 --- a/modules/client/client.go +++ b/modules/client/client.go @@ -24,12 +24,21 @@ func NewFactory(cfg *config.Config, wrappers []DriverWrapper) *Factory { } } -func (f *Factory) Get(name string, errDecoder ErrDecoder, driverWrappers ...DriverWrapper) *Base { +func (f *Factory) Get(name string, opts ...Option) *Base { + cOpts := &options{ + errDecoder: DefaultErrDecoder(name), + driverWrappers: []DriverWrapper{}, + } + + for _, opt := range opts { + opt.Apply(cOpts) + } + return &Base{ driver: newDriverBuilder(f.cfg.Of("client").Of(name)). - AddErrDecoder(errDecoder). + AddErrDecoder(cOpts.errDecoder). AddUpdaters(f.baseWrappers...). - AddUpdaters(driverWrappers...). + AddUpdaters(cOpts.driverWrappers...). build(), name: name, } diff --git a/modules/client/error.go b/modules/client/error.go index 7ea721c..b468406 100644 --- a/modules/client/error.go +++ b/modules/client/error.go @@ -2,16 +2,77 @@ package client import ( "context" + "encoding/json" + "fmt" + "strings" "github.com/go-resty/resty/v2" - "github.com/gofiber/fiber/v2" ) type ErrDecoder func(context.Context, *resty.Response) error -func DefaultErrDecoder(_ context.Context, res *resty.Response) error { - if res.StatusCode() > 300 { - return fiber.NewError(res.StatusCode(), "client error") +type GenericClientError struct { + ClientName string + StatusCode int + RawBody []byte + ParsedBody interface{} +} + +func (e GenericClientError) Error() string { + msg := fmt.Sprintf("Error on client %s (Status %d)", e.ClientName, e.StatusCode) + if details := e.extractErrorDetails(); details != "" { + msg += ": " + details + } + return msg +} + +func (e GenericClientError) extractErrorDetails() string { + var details []string + + var extract func(interface{}) + extract = func(v interface{}) { + switch value := v.(type) { + case string: + details = append(details, strings.TrimSpace(value)) + case map[string]interface{}: + for _, v := range value { + extract(v) + } + case []interface{}: + for _, v := range value { + extract(v) + } + } + } + + extract(e.ParsedBody) + + if len(details) == 0 && len(e.RawBody) > 0 { + return strings.TrimSpace(string(e.RawBody)) + } + + return strings.Join(details, "; ") +} + +func DefaultErrDecoder(name string) ErrDecoder { + return func(_ context.Context, res *resty.Response) error { + if res.IsSuccess() { + return nil + } + + apiErr := GenericClientError{ + ClientName: name, + StatusCode: res.StatusCode(), + RawBody: res.Body(), + } + + var jsonBody interface{} + if err := json.Unmarshal(res.Body(), &jsonBody); err == nil { + apiErr.ParsedBody = jsonBody + } else { + apiErr.ParsedBody = string(res.Body()) + } + + return apiErr } - return nil } diff --git a/modules/client/option.go b/modules/client/option.go new file mode 100644 index 0000000..d527f23 --- /dev/null +++ b/modules/client/option.go @@ -0,0 +1,28 @@ +package client + +type options struct { + errDecoder ErrDecoder + driverWrappers []DriverWrapper +} + +type Option interface { + Apply(*options) +} + +type withOption func(*options) + +func (wf withOption) Apply(opts *options) { + wf(opts) +} + +func WithErrDecoder(ed ErrDecoder) Option { + return withOption(func(o *options) { + o.errDecoder = ed + }) +} + +func WithDriverWrappers(wrappers ...DriverWrapper) Option { + return withOption(func(o *options) { + o.driverWrappers = append(o.driverWrappers, wrappers...) + }) +}