1
0
mirror of synced 2024-11-25 06:26:03 +03:00

Custom error type instead of builtin

This commit is contained in:
Pavel 2022-10-28 13:08:45 +03:00 committed by GitHub
commit d40fe94ec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 401 additions and 188 deletions

View File

@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go-version: ['1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17'] go-version: ['1.13', '1.14', '1.15', '1.16', '1.17']
steps: steps:
- name: Set up Go ${{ matrix.go-version }} - name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v2 uses: actions/setup-go@v2

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/retailcrm/mg-transport-api-client-go module github.com/retailcrm/mg-transport-api-client-go
go 1.11 go 1.13
require ( require (
github.com/google/go-querystring v1.0.0 github.com/google/go-querystring v1.0.0

1
go.sum
View File

@ -15,7 +15,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -71,7 +71,7 @@ func (c *MgClient) TransportTemplates() ([]Template, int, error) {
} }
if status > http.StatusCreated || status < http.StatusOK { if status > http.StatusCreated || status < http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -116,7 +116,7 @@ func (c *MgClient) ActivateTemplate(channelID uint64, request ActivateTemplateRe
} }
if status > http.StatusCreated || status < http.StatusOK { if status > http.StatusCreated || status < http.StatusOK {
return status, c.Error(data) return status, NewAPIClientError(data)
} }
return status, err return status, err
@ -165,7 +165,7 @@ func (c *MgClient) UpdateTemplate(request Template) (int, error) {
} }
if status != http.StatusOK { if status != http.StatusOK {
return status, c.Error(data) return status, NewAPIClientError(data)
} }
return status, err return status, err
@ -190,7 +190,7 @@ func (c *MgClient) DeactivateTemplate(channelID uint64, templateCode string) (in
} }
if status > http.StatusCreated || status < http.StatusOK { if status > http.StatusCreated || status < http.StatusOK {
return status, c.Error(data) return status, NewAPIClientError(data)
} }
return status, err return status, err
@ -224,7 +224,7 @@ func (c *MgClient) TransportChannels(request Channels) ([]ChannelListItem, int,
} }
if status > http.StatusCreated || status < http.StatusOK { if status > http.StatusCreated || status < http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -283,7 +283,7 @@ func (c *MgClient) ActivateTransportChannel(request Channel) (ActivateResponse,
} }
if status > http.StatusCreated || status < http.StatusOK { if status > http.StatusCreated || status < http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -342,7 +342,7 @@ func (c *MgClient) UpdateTransportChannel(request Channel) (UpdateResponse, int,
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -378,7 +378,7 @@ func (c *MgClient) DeactivateTransportChannel(id uint64) (DeleteResponse, int, e
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -427,7 +427,7 @@ func (c *MgClient) Messages(request SendData) (MessagesResponse, int, error) {
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -471,7 +471,7 @@ func (c *MgClient) UpdateMessages(request EditMessageRequest) (MessagesResponse,
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -510,7 +510,7 @@ func (c *MgClient) MarkMessageRead(request MarkMessageReadRequest) (MarkMessageR
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -541,7 +541,7 @@ func (c *MgClient) AckMessage(request AckMessageRequest) (int, error) {
} }
if status != http.StatusOK { if status != http.StatusOK {
return status, c.Error(data) return status, NewAPIClientError(data)
} }
return status, err return status, err
@ -579,7 +579,7 @@ func (c *MgClient) DeleteMessage(request DeleteData) (*MessagesResponse, int, er
return nil, status, err return nil, status, err
} }
if status != http.StatusOK { if status != http.StatusOK {
return nil, status, c.Error(data) return nil, status, NewAPIClientError(data)
} }
var previousChatMessage *MessagesResponse var previousChatMessage *MessagesResponse
@ -618,7 +618,7 @@ func (c *MgClient) GetFile(request string) (FullFileResponse, int, error) {
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -638,7 +638,7 @@ func (c *MgClient) UploadFile(request io.Reader) (UploadFileResponse, int, error
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
@ -659,24 +659,12 @@ func (c *MgClient) UploadFileByURL(request UploadFileByUrlRequest) (UploadFileRe
} }
if status != http.StatusOK { if status != http.StatusOK {
return resp, status, c.Error(data) return resp, status, NewAPIClientError(data)
} }
return resp, status, err return resp, status, err
} }
func (c *MgClient) Error(info []byte) error {
var data map[string]interface{}
if err := json.Unmarshal(info, &data); err != nil {
return err
}
values := data["errors"].([]interface{})
return errors.New(values[0].(string))
}
// MakeTimestamp returns current unix timestamp. // MakeTimestamp returns current unix timestamp.
func MakeTimestamp() int64 { func MakeTimestamp() int64 {
return time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) return time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))

View File

@ -3,6 +3,7 @@ package v1
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -45,61 +46,65 @@ func (t *MGClientTest) Test_TransportChannels() {
t.gock(). t.gock().
Get(t.transportURL("channels")). Get(t.transportURL("channels")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON([]ChannelListItem{{ JSON(
ID: 1, []ChannelListItem{
ExternalID: "external_id", {
Type: "whatsapp", ID: 1,
Name: &chName, ExternalID: "external_id",
Settings: ChannelSettings{ Type: "whatsapp",
Status: Status{ Name: &chName,
Delivered: ChannelFeatureNone, Settings: ChannelSettings{
Read: ChannelFeatureSend, Status: Status{
}, Delivered: ChannelFeatureNone,
Text: ChannelSettingsText{ Read: ChannelFeatureSend,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, Text: ChannelSettingsText{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
MaxCharsCount: 4096, Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Product: Product{ MaxCharsCount: 4096,
Creating: ChannelFeatureReceive, },
Editing: ChannelFeatureReceive, Product: Product{
}, Creating: ChannelFeatureReceive,
Order: Order{ Editing: ChannelFeatureReceive,
Creating: ChannelFeatureReceive, },
Editing: ChannelFeatureReceive, Order: Order{
}, Creating: ChannelFeatureReceive,
File: ChannelSettingsFilesBase{ Editing: ChannelFeatureReceive,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, File: ChannelSettingsFilesBase{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
Max: 1, Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Image: ChannelSettingsFilesBase{ Max: 1,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, Image: ChannelSettingsFilesBase{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
Max: 1, // nolint:gomnd Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Suggestions: ChannelSettingsSuggestions{ Max: 1, // nolint:gomnd
Text: ChannelFeatureBoth, },
Phone: ChannelFeatureBoth, Suggestions: ChannelSettingsSuggestions{
Email: ChannelFeatureBoth, Text: ChannelFeatureBoth,
}, Phone: ChannelFeatureBoth,
CustomerExternalID: ChannelFeatureCustomerExternalIDPhone, Email: ChannelFeatureBoth,
SendingPolicy: SendingPolicy{ },
NewCustomer: ChannelFeatureSendingPolicyTemplate, CustomerExternalID: ChannelFeatureCustomerExternalIDPhone,
SendingPolicy: SendingPolicy{
NewCustomer: ChannelFeatureSendingPolicyTemplate,
},
},
CreatedAt: createdAt,
UpdatedAt: &createdAt,
ActivatedAt: createdAt,
DeactivatedAt: nil,
IsActive: true,
}, },
}, },
CreatedAt: createdAt, )
UpdatedAt: &createdAt,
ActivatedAt: createdAt,
DeactivatedAt: nil,
IsActive: true,
}})
data, status, err := c.TransportChannels(Channels{Active: true}) data, status, err := c.TransportChannels(Channels{Active: true})
t.Require().NoError(err) t.Require().NoError(err)
@ -147,11 +152,13 @@ func (t *MGClientTest) Test_ActivateTransportChannel() {
t.gock(). t.gock().
Post(t.transportURL("channels")). Post(t.transportURL("channels")).
Reply(http.StatusCreated). Reply(http.StatusCreated).
JSON(ActivateResponse{ JSON(
ChannelID: 1, ActivateResponse{
ExternalID: "external_id_1", ChannelID: 1,
ActivatedAt: time.Now(), ExternalID: "external_id_1",
}) ActivatedAt: time.Now(),
},
)
data, status, err := c.ActivateTransportChannel(ch) data, status, err := c.ActivateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -200,19 +207,23 @@ func (t *MGClientTest) Test_ActivateNewTransportChannel() {
t.gock(). t.gock().
Post(t.transportURL("channels")). Post(t.transportURL("channels")).
Reply(http.StatusCreated). Reply(http.StatusCreated).
JSON(ActivateResponse{ JSON(
ChannelID: 1, ActivateResponse{
ExternalID: "external_id_1", ChannelID: 1,
ActivatedAt: time.Now(), ExternalID: "external_id_1",
}) ActivatedAt: time.Now(),
},
)
t.gock(). t.gock().
Delete(t.transportURL("channels/1")). Delete(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(DeleteResponse{ JSON(
ChannelID: 1, DeleteResponse{
DeactivatedAt: time.Now(), ChannelID: 1,
}) DeactivatedAt: time.Now(),
},
)
data, status, err := c.ActivateTransportChannel(ch) data, status, err := c.ActivateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -266,11 +277,13 @@ func (t *MGClientTest) Test_UpdateTransportChannel() {
t.gock(). t.gock().
Put(t.transportURL("channels/1")). Put(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(UpdateResponse{ JSON(
ChannelID: uint64(1), UpdateResponse{
ExternalID: "external_id_1", ChannelID: uint64(1),
UpdatedAt: time.Now(), ExternalID: "external_id_1",
}) UpdatedAt: time.Now(),
},
)
data, status, err := c.UpdateTransportChannel(ch) data, status, err := c.UpdateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -287,27 +300,31 @@ func (t *MGClientTest) Test_TransportTemplates() {
t.gock(). t.gock().
Get(t.transportURL("templates")). Get(t.transportURL("templates")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON([]Template{{ JSON(
Code: "tpl_code", []Template{
ChannelID: 1,
Name: "Test Template",
Enabled: true,
Type: TemplateTypeText,
Template: []TemplateItem{
{ {
Type: TemplateItemTypeText, Code: "tpl_code",
Text: "Hello, ", ChannelID: 1,
}, Name: "Test Template",
{ Enabled: true,
Type: TemplateItemTypeVar, Type: TemplateTypeText,
VarType: TemplateVarFirstName, Template: []TemplateItem{
}, {
{ Type: TemplateItemTypeText,
Type: TemplateItemTypeText, Text: "Hello, ",
Text: "! We're glad to see you back in our store.", },
{
Type: TemplateItemTypeVar,
VarType: TemplateVarFirstName,
},
{
Type: TemplateItemTypeText,
Text: "! We're glad to see you back in our store.",
},
},
}, },
}, },
}}) )
data, status, err := c.TransportTemplates() data, status, err := c.TransportTemplates()
t.Assert().NoError(err, fmt.Sprintf("%d %s", status, err)) t.Assert().NoError(err, fmt.Sprintf("%d %s", status, err))
@ -390,10 +407,12 @@ func (t *MGClientTest) Test_UpdateTemplate() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Filter(func(r *http.Request) bool { Filter(
return r.Method == http.MethodPut && func(r *http.Request) bool {
r.URL.Path == "/api/transport/v1/channels/1/templates/encodable#code" return r.Method == http.MethodPut &&
}). r.URL.Path == "/api/transport/v1/channels/1/templates/encodable#code"
},
).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(map[string]interface{}{}) JSON(map[string]interface{}{})
@ -440,9 +459,11 @@ func (t *MGClientTest) Test_UpdateTemplateFail() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Reply(http.StatusBadRequest). Reply(http.StatusBadRequest).
JSON(map[string][]string{ JSON(
"errors": {"Some weird error message..."}, map[string][]string{
}) "errors": {"Some weird error message..."},
},
)
status, err := c.UpdateTemplate(tpl) status, err := c.UpdateTemplate(tpl)
t.Assert().Error(err, fmt.Sprintf("%d %s", status, err)) t.Assert().Error(err, fmt.Sprintf("%d %s", status, err))
@ -453,10 +474,12 @@ func (t *MGClientTest) Test_DeactivateTemplate() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Filter(func(r *http.Request) bool { Filter(
return r.Method == http.MethodDelete && func(r *http.Request) bool {
r.URL.Path == t.transportURL("channels/1/templates/test_template#code") return r.Method == http.MethodDelete &&
}). r.URL.Path == t.transportURL("channels/1/templates/test_template#code")
},
).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(map[string]interface{}{}) JSON(map[string]interface{}{})
@ -487,10 +510,12 @@ func (t *MGClientTest) Test_TextMessages() {
t.gock(). t.gock().
Post(t.transportURL("messages")). Post(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
data, status, err := c.Messages(snd) data, status, err := c.Messages(snd)
t.Require().NoError(err) t.Require().NoError(err)
@ -507,26 +532,32 @@ func (t *MGClientTest) Test_ImageMessages() {
t.gock(). t.gock().
Post(t.transportURL("files/upload_by_url")). Post(t.transportURL("files/upload_by_url")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(UploadFileResponse{ JSON(
ID: "1", UploadFileResponse{
Hash: "1", ID: "1",
Type: "image/png", Hash: "1",
MimeType: "", Type: "image/png",
Size: 1024, MimeType: "",
CreatedAt: time.Now(), Size: 1024,
}) CreatedAt: time.Now(),
},
)
t.gock(). t.gock().
Post(t.transportURL("messages")). Post(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
uploadFileResponse, st, err := c.UploadFileByURL(UploadFileByUrlRequest{ uploadFileResponse, st, err := c.UploadFileByURL(
Url: "https://via.placeholder.com/1", UploadFileByUrlRequest{
}) Url: "https://via.placeholder.com/1",
},
)
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, st) t.Assert().Equal(http.StatusOK, st)
t.Assert().Equal("1", uploadFileResponse.ID) t.Assert().Equal("1", uploadFileResponse.ID)
@ -569,10 +600,12 @@ func (t *MGClientTest) Test_UpdateMessages() {
t.gock(). t.gock().
Put(t.transportURL("messages")). Put(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
dataU, status, err := c.UpdateMessages(sndU) dataU, status, err := c.UpdateMessages(sndU)
t.Require().NoError(err) t.Require().NoError(err)
@ -599,28 +632,34 @@ func (t *MGClientTest) Test_MarkMessageReadAndDelete() {
t.gock(). t.gock().
Delete(t.transportURL("messages")). Delete(t.transportURL("messages")).
JSON(DeleteData{ JSON(
Message: Message{ DeleteData{
ExternalID: "deleted", Message: Message{
ExternalID: "deleted",
},
Channel: 1,
}, },
Channel: 1, ).
}).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 2, MessagesResponse{
Time: time.Now(), MessageID: 2,
}) Time: time.Now(),
},
)
_, status, err := c.MarkMessageRead(snd) _, status, err := c.MarkMessageRead(snd)
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, status) t.Assert().Equal(http.StatusOK, status)
previousChatMessage, status, err := c.DeleteMessage(DeleteData{ previousChatMessage, status, err := c.DeleteMessage(
Message{ DeleteData{
ExternalID: "deleted", Message{
ExternalID: "deleted",
},
1,
}, },
1, )
})
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, status) t.Assert().Equal(http.StatusOK, status)
t.Assert().Equal(2, previousChatMessage.MessageID) t.Assert().Equal(2, previousChatMessage.MessageID)
@ -633,10 +672,12 @@ func (t *MGClientTest) Test_DeactivateTransportChannel() {
t.gock(). t.gock().
Delete(t.transportURL("channels/1")). Delete(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(DeleteResponse{ JSON(
ChannelID: 1, DeleteResponse{
DeactivatedAt: time.Now(), ChannelID: 1,
}) DeactivatedAt: time.Now(),
},
)
deleteData, status, err := c.DeactivateTransportChannel(1) deleteData, status, err := c.DeactivateTransportChannel(1)
t.Require().NoError(err) t.Require().NoError(err)
@ -678,3 +719,35 @@ func (t *MGClientTest) Test_UploadFile() {
resp.CreatedAt = data.CreatedAt resp.CreatedAt = data.CreatedAt
t.Assert().Equal(resp, data) t.Assert().Equal(resp, data)
} }
func (t *MGClientTest) Test_SuccessHandleError() {
client := t.client()
json := `{"errors": ["Channel not found"]}`
defer gock.Off()
t.gock().
Delete(t.transportURL("channels/123")).
Reply(http.StatusInternalServerError)
t.gock().
Delete(t.transportURL("channels/455")).
Reply(http.StatusBadRequest).
JSON(json)
_, statusCode, err := client.DeactivateTransportChannel(123)
t.Assert().Equal(http.StatusInternalServerError, statusCode)
t.Assert().IsType(new(HTTPClientError), err)
t.Assert().Equal(internalServerError, err.Error())
var serverErr *HTTPClientError
if errors.As(err, &serverErr) {
t.Assert().Nil(serverErr.Response)
} else {
t.Fail("Unexpected type of error")
}
_, statusCode, err = client.DeactivateTransportChannel(455)
t.Assert().Equal(http.StatusBadRequest, statusCode)
t.Assert().IsType(new(HTTPClientError), err)
t.Assert().Equal("Channel not found", err.Error())
}

77
v1/errors.go Normal file
View File

@ -0,0 +1,77 @@
package v1
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
var defaultErrorMessage = "http client error"
var internalServerError = "internal server error"
var marshalError = "cannot unmarshal response body"
type MGErrors struct {
Errors []string
}
type HTTPClientError struct {
ErrorMsg string
BaseError error
Response io.Reader
}
func (err *HTTPClientError) Unwrap() error {
return err.BaseError
}
func (err *HTTPClientError) Error() string {
message := defaultErrorMessage
if err.BaseError != nil {
message = fmt.Sprintf("%s: %s", defaultErrorMessage, err.BaseError.Error())
} else if len(err.ErrorMsg) > 0 {
message = err.ErrorMsg
}
return message
}
func NewCriticalHTTPError(err error) error {
return &HTTPClientError{BaseError: err}
}
func NewAPIClientError(responseBody []byte) error {
var data MGErrors
var message string
if len(responseBody) == 0 {
message = internalServerError
} else {
err := json.Unmarshal(responseBody, &data)
if err != nil {
message = marshalError
} else if len(data.Errors) > 0 {
message = data.Errors[0]
}
}
return &HTTPClientError{ErrorMsg: message}
}
func NewServerError(response *http.Response) error {
var serverError *HTTPClientError
body, _ := buildLimitedRawResponse(response)
err := NewAPIClientError(body)
if errors.As(err, &serverError) && len(body) > 0 {
serverError.Response = bytes.NewBuffer(body)
return serverError
}
return err
}

66
v1/errors_test.go Normal file
View File

@ -0,0 +1,66 @@
package v1
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewCriticalHTTPError(t *testing.T) {
err := &url.Error{Op: "Get", URL: "http//example.com", Err: errors.New("EOF")}
httpErr := NewCriticalHTTPError(err)
assert.IsType(t, new(HTTPClientError), httpErr)
assert.IsType(t, new(url.Error), errors.Unwrap(httpErr))
assert.IsType(t, new(url.Error), errors.Unwrap(httpErr))
assert.Equal(t, httpErr.Error(), fmt.Sprintf("%s: %s", defaultErrorMessage, err.Error()))
}
func TestNewApiClientError(t *testing.T) {
body := []byte(`{"errors" : ["Channel not found"]}`)
httpErr := NewAPIClientError(body)
assert.IsType(t, new(HTTPClientError), httpErr)
assert.Equal(t, httpErr.Error(), "Channel not found")
body = []byte{}
httpErr = NewAPIClientError(body)
assert.IsType(t, new(HTTPClientError), httpErr)
assert.Equal(t, httpErr.Error(), internalServerError)
}
func TestNewServerError(t *testing.T) {
body := []byte(`{"errors" : ["Something went wrong"]}`)
response := new(http.Response)
response.Body = ioutil.NopCloser(bytes.NewReader(body))
serverErr := NewServerError(response)
assert.IsType(t, new(HTTPClientError), serverErr)
assert.Equal(t, serverErr.Error(), "Something went wrong")
var err *HTTPClientError
if errors.As(serverErr, &err) {
assert.NotNil(t, err.Response)
} else {
t.Fatal("Unexpected type of error")
}
body = []byte(`{"invalid_json"`)
response = new(http.Response)
response.Body = ioutil.NopCloser(bytes.NewReader(body))
serverErr = NewServerError(response)
assert.IsType(t, new(HTTPClientError), serverErr)
assert.Equal(t, serverErr.Error(), marshalError)
if errors.As(serverErr, &err) {
assert.NotNil(t, err.Response)
}
}

22
v1/helpers.go Normal file
View File

@ -0,0 +1,22 @@
package v1
import (
"io"
"io/ioutil"
"net/http"
)
const MB = 1 << 20
func buildLimitedRawResponse(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
limitReader := io.LimitReader(resp.Body, MB)
body, err := ioutil.ReadAll(limitReader)
if err != nil {
return body, err
}
return body, nil
}

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
) )
@ -71,15 +70,15 @@ func makeRequest(reqType, url string, buf io.Reader, c *MgClient) ([]byte, int,
resp, err := c.httpClient.Do(req) resp, err := c.httpClient.Do(req)
if err != nil { if err != nil {
return res, 0, err return res, 0, NewCriticalHTTPError(err)
} }
if resp.StatusCode >= http.StatusInternalServerError { if resp.StatusCode >= http.StatusInternalServerError {
err = fmt.Errorf("http request error. status code: %d", resp.StatusCode) err = NewServerError(resp)
return res, resp.StatusCode, err return res, resp.StatusCode, err
} }
res, err = buildRawResponse(resp) res, err = buildLimitedRawResponse(resp)
if err != nil { if err != nil {
return res, 0, err return res, 0, err
} }
@ -90,14 +89,3 @@ func makeRequest(reqType, url string, buf io.Reader, c *MgClient) ([]byte, int,
return res, resp.StatusCode, err return res, resp.StatusCode, err
} }
func buildRawResponse(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
res, err := ioutil.ReadAll(resp.Body)
if err != nil {
return res, err
}
return res, nil
}