| package internal |
|
|
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "net/http" |
| "strings" |
| "time" |
|
|
| "github.com/samber/lo" |
| svix "github.com/svix/svix-webhooks/go" |
|
|
| "github.com/openmeterio/openmeter/openmeter/notification/webhook" |
| ) |
|
|
| var _ error = (*SvixError)(nil) |
|
|
| type SvixError struct { |
| HTTPStatus int |
| Code *string |
| Details []string |
|
|
| RetryAfter time.Duration |
| } |
|
|
| func (e SvixError) Error() string { |
| var out []byte |
| buf := bytes.NewBuffer(out) |
|
|
| buf.WriteString(lo.FromPtrOr(e.Code, "unknown svix error")) |
| buf.WriteString(": ") |
| buf.WriteString(strings.Join(e.Details, ", ")) |
|
|
| return buf.String() |
| } |
|
|
| func (e SvixError) Wrap() error { |
| switch e.HTTPStatus { |
| case http.StatusBadRequest, http.StatusConflict, http.StatusUnprocessableEntity: |
| return webhook.NewUnrecoverableError(webhook.NewValidationError(e)) |
| case http.StatusNotFound: |
| return webhook.NewNotFoundError(e) |
| case http.StatusTooManyRequests: |
| return webhook.NewRetryableError(e, e.RetryAfter) |
| case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: |
| return webhook.NewRetryableError(e, e.RetryAfter) |
| default: |
| return e |
| } |
| } |
|
|
| type SvixErrorBody struct { |
| Code string `json:"code"` |
| Detail string `json:"detail"` |
| } |
|
|
| type SvixValidationErrorBody struct { |
| Detail []SvixValidationError `json:"detail"` |
| } |
|
|
| type SvixValidationError struct { |
| Loc []string `json:"loc"` |
| Message string `json:"msg"` |
| Type string `json:"type"` |
| } |
|
|
| func WrapSvixError(err error) error { |
| if err == nil { |
| return nil |
| } |
|
|
| svixErr, ok := lo.ErrorsAs[*svix.Error](err) |
| if !ok { |
| return err |
| } |
|
|
| switch svixErr.Status() { |
| case http.StatusUnprocessableEntity: |
| var body SvixValidationErrorBody |
|
|
| if e := json.Unmarshal(svixErr.Body(), &body); e != nil { |
| return fmt.Errorf("failed to parse Svix error response: %w", err) |
| } |
|
|
| return SvixError{ |
| HTTPStatus: svixErr.Status(), |
| Code: lo.ToPtr("validation_error"), |
| Details: lo.Map(body.Detail, func(item SvixValidationError, _ int) string { |
| loc := strings.Join(item.Loc, ".") |
|
|
| return fmt.Sprintf("[location=%s type=%s]: %s", loc, item.Type, item.Message) |
| }), |
| }.Wrap() |
| default: |
| var body SvixErrorBody |
|
|
| if e := json.Unmarshal(svixErr.Body(), &body); e != nil { |
| return fmt.Errorf("failed to parse Svix error response: %w", err) |
| } |
|
|
| return SvixError{ |
| HTTPStatus: svixErr.Status(), |
| Code: &body.Code, |
| Details: []string{ |
| body.Detail, |
| }, |
| }.Wrap() |
| } |
| } |
|
|