Skip to content

Commit

Permalink
refactor: client err decoder
Browse files Browse the repository at this point in the history
- make factory.Get method accepts options to provide default err decoder without declaration

- refactored defaul error decoder to provide more detailed information on error responses from clients
  • Loading branch information
ispiroglu committed Sep 20, 2024
1 parent 82e1e6c commit 92dabfb
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 8 deletions.
15 changes: 12 additions & 3 deletions modules/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
71 changes: 66 additions & 5 deletions modules/client/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
28 changes: 28 additions & 0 deletions modules/client/option.go
Original file line number Diff line number Diff line change
@@ -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...)
})
}

0 comments on commit 92dabfb

Please sign in to comment.