mirror of
https://github.com/retailcrm/api-client-go.git
synced 2024-11-21 20:36:03 +03:00
Correct v2 (#55)
* refactor library and upgrade version * remove useless environment variable * remove unsupported versions from go.mod * fixes for error handling & data types * different improvements for errors * fixes for types and tests * better coverage, fix error with the unmarshalers
This commit is contained in:
parent
269764175e
commit
97b1abf470
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -9,7 +9,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DEVELOPER_NODE: 1
|
|
||||||
RETAILCRM_URL: https://test.retailcrm.pro
|
RETAILCRM_URL: https://test.retailcrm.pro
|
||||||
RETAILCRM_KEY: key
|
RETAILCRM_KEY: key
|
||||||
|
|
||||||
@ -41,7 +40,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']
|
||||||
include:
|
include:
|
||||||
- go-version: '1.17'
|
- go-version: '1.17'
|
||||||
coverage: 1
|
coverage: 1
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
# Folders
|
# Folders
|
||||||
_obj
|
_obj
|
||||||
|
vendor
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
# Architecture specific extensions/prefixes
|
||||||
*.[568vq]
|
*.[568vq]
|
||||||
|
@ -146,9 +146,9 @@ linters-settings:
|
|||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 25
|
min-complexity: 25
|
||||||
goimports:
|
goimports:
|
||||||
local-prefixes: github.com/retailcrm/api-client-go
|
local-prefixes: github.com/retailcrm/api-client-go/v2
|
||||||
lll:
|
lll:
|
||||||
line-length: 120
|
line-length: 160
|
||||||
maligned:
|
maligned:
|
||||||
suggest-new: true
|
suggest-new: true
|
||||||
misspell:
|
misspell:
|
||||||
@ -180,6 +180,9 @@ issues:
|
|||||||
- gocognit
|
- gocognit
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- godot
|
- godot
|
||||||
|
- gocritic
|
||||||
|
- gosec
|
||||||
|
- staticcheck
|
||||||
exclude-use-default: true
|
exclude-use-default: true
|
||||||
exclude-case-sensitive: false
|
exclude-case-sensitive: false
|
||||||
max-issues-per-linter: 0
|
max-issues-per-linter: 0
|
||||||
|
109
README.md
109
README.md
@ -10,51 +10,51 @@
|
|||||||
|
|
||||||
This is golang RetailCRM API client.
|
This is golang RetailCRM API client.
|
||||||
|
|
||||||
## Install
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -x github.com/retailcrm/api-client-go
|
go get -u github.com/retailcrm/api-client-go/v2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```golang
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/retailcrm/api-client-go/v5"
|
"github.com/retailcrm/api-client-go/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var client = v5.New("https://demo.retailcrm.pro", "09jIJ09j0JKhgyfvyuUIKhiugF")
|
var client = retailcrm.New("https://demo.retailcrm.pro", "09jIJ09j0JKhgyfvyuUIKhiugF")
|
||||||
|
|
||||||
data, status, err := client.Orders(v5.OrdersRequest{
|
data, status, err := client.Orders(retailcrm.OrdersRequest{
|
||||||
Filter: v5.OrdersFilter{},
|
Filter: retailcrm.OrdersFilter{},
|
||||||
Limit: 20,
|
Limit: 20,
|
||||||
Page: 1,
|
Page: 1,
|
||||||
},)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v", err.Error())
|
if apiErr, ok := retailcrm.AsAPIError(err); ok {
|
||||||
}
|
log.Fatalf("http status: %d, %s", status, apiErr.String())
|
||||||
|
}
|
||||||
|
|
||||||
if status >= http.StatusBadRequest {
|
log.Fatalf("http status: %d, error: %s", status, err)
|
||||||
fmt.Printf("%v", err.ApiError())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, value := range data.Orders {
|
for _, value := range data.Orders {
|
||||||
fmt.Printf("%v\n", value.Email)
|
log.Printf("%v\n", value.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(data.Orders[1].FirstName)
|
log.Println(data.Orders[1].FirstName)
|
||||||
|
|
||||||
idata, status, err := c.InventoriesUpload(
|
inventories, status, err := client.InventoriesUpload([]retailcrm.InventoryUpload{
|
||||||
[]InventoryUpload{
|
|
||||||
{
|
{
|
||||||
XMLID: "pTKIKAeghYzX21HTdzFCe1",
|
XMLID: "pTKIKAeghYzX21HTdzFCe1",
|
||||||
Stores: []InventoryUploadStore{
|
Stores: []retailcrm.InventoryUploadStore{
|
||||||
{
|
{
|
||||||
Code: "test-store-v5",
|
Code: "test-store-v5",
|
||||||
Available: 10,
|
Available: 10,
|
||||||
@ -74,7 +74,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
XMLID: "JQIvcrCtiSpOV3AAfMiQB3",
|
XMLID: "JQIvcrCtiSpOV3AAfMiQB3",
|
||||||
Stores: []InventoryUploadStore{
|
Stores: []retailcrm.InventoryUploadStore{
|
||||||
{
|
{
|
||||||
Code: "test-store-v5",
|
Code: "test-store-v5",
|
||||||
Available: 45,
|
Available: 45,
|
||||||
@ -95,13 +95,70 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%v", err.Error())
|
if apiErr, ok := retailcrm.AsAPIError(err); ok {
|
||||||
|
log.Fatalf("http status: %d, %s", status, apiErr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatalf("http status: %d, error: %s", status, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status >= http.StatusBadRequest {
|
log.Println(inventories.ProcessedOffersCount)
|
||||||
fmt.Printf("%v", err.ApiError())
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(idata.processedOffersCount)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can use different error types and `retailcrm.AsAPIError` to process client errors. Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/retailcrm/api-client-go/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var client = retailcrm.New("https://demo.retailcrm.pro", "09jIJ09j0JKhgyfvyuUIKhiugF")
|
||||||
|
|
||||||
|
resp, status, err := client.APICredentials()
|
||||||
|
if err != nil {
|
||||||
|
apiErr, ok := retailcrm.AsAPIError(err)
|
||||||
|
if !ok {
|
||||||
|
log.Fatalf("http status: %d, error: %s", status, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrMissingCredentials):
|
||||||
|
log.Fatalln("No API key provided.")
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrInvalidCredentials):
|
||||||
|
log.Fatalln("Invalid API key.")
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrAccessDenied):
|
||||||
|
log.Fatalln("Access denied. Please check that the provided key has access to the credentials info.")
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrAccountDoesNotExist):
|
||||||
|
log.Fatalln("There is no RetailCRM at the provided URL.")
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrMissingParameter):
|
||||||
|
// retailcrm.APIError in this case will always contain "Name" key in the errors list with the parameter name.
|
||||||
|
log.Fatalln("This parameter should be present:", apiErr.Errors()["Name"])
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrValidation):
|
||||||
|
log.Println("Validation errors from the API:")
|
||||||
|
|
||||||
|
for name, value := range apiErr.Errors() {
|
||||||
|
log.Printf(" - %s: %s\n", name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
case errors.Is(apiErr, retailcrm.ErrGeneric):
|
||||||
|
log.Fatalf("failure from the API. %s", apiErr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Available scopes:", strings.Join(resp.Scopes, ", "))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
Please check the [UPGRADING.md](UPGRADING.md) to learn how to upgrade to the new version.
|
||||||
|
70
UPGRADING.md
Normal file
70
UPGRADING.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Upgrading to the v2
|
||||||
|
|
||||||
|
### Install the new version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/retailcrm/api-client-go/v2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update all imports
|
||||||
|
|
||||||
|
Before:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import v5 "github.com/retailcrm/api-client-go/v5"
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/retailcrm/api-client-go/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use package alias `v5` to skip the second step.
|
||||||
|
|
||||||
|
### Replace package name for all imported symbols
|
||||||
|
|
||||||
|
Before:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import v5 "github.com/retailcrm/api-client-go/v5"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := v5.New("https://test.retailcrm.pro", "key")
|
||||||
|
data, status, err := client.Orders(v5.OrdersRequest{
|
||||||
|
Filter: v5.OrdersFilter{
|
||||||
|
City: "Moscow",
|
||||||
|
},
|
||||||
|
Page: 1,
|
||||||
|
})
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/retailcrm/api-client-go/v2"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
client := retailcrm.New("https://test.retailcrm.pro", "key")
|
||||||
|
data, status, err := client.Orders(retailcrm.OrdersRequest{
|
||||||
|
Filter: retailcrm.OrdersFilter{
|
||||||
|
City: "Moscow",
|
||||||
|
},
|
||||||
|
Page: 1,
|
||||||
|
})
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upgrade client usages
|
||||||
|
|
||||||
|
This major release contains some breaking changes regarding field names and fully redesigned error handling. Use the second example from
|
||||||
|
the readme to learn how to process errors correctly.
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -20,14 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
if os.Getenv("DEVELOPER_NODE") == "1" {
|
err := godotenv.Load(".env")
|
||||||
err := godotenv.Load("../.env")
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal("Error loading .env file")
|
||||||
log.Fatal("Error loading .env file")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -494,7 +492,7 @@ func TestClient_CustomersUpload_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(460).
|
Reply(460).
|
||||||
BodyString(`{"success": false, "errorMsg": "Customers are loaded with errors"}`)
|
BodyString(`{"success": false, "errorMsg": "Customers are loaded with ErrorsList"}`)
|
||||||
|
|
||||||
data, _, err := c.CustomersUpload(customers)
|
data, _, err := c.CustomersUpload(customers)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -631,7 +629,7 @@ func TestClient_CustomersFixExternalIds_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"id": "ID must be an integer"}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"id": "ID must be an integer"}}`)
|
||||||
|
|
||||||
data, _, err := c.CustomersFixExternalIds(customers)
|
data, _, err := c.CustomersFixExternalIds(customers)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -690,7 +688,7 @@ func TestClient_CustomersHistory_Fail(t *testing.T) {
|
|||||||
Get("/customers/history").
|
Get("/customers/history").
|
||||||
MatchParam("filter[startDate]", "2020-13-12").
|
MatchParam("filter[startDate]", "2020-13-12").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"children[startDate]": "Значение недопустимо."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"children[startDate]": "Значение недопустимо."}}`)
|
||||||
|
|
||||||
data, _, err := c.CustomersHistory(f)
|
data, _, err := c.CustomersHistory(f)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -885,7 +883,7 @@ func TestClient_CorporateCustomersFixExternalIds_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"id": "ID must be an integer"}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"id": "ID must be an integer"}}`)
|
||||||
|
|
||||||
data, _, err := c.CorporateCustomersFixExternalIds(customers)
|
data, _, err := c.CorporateCustomersFixExternalIds(customers)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -944,7 +942,7 @@ func TestClient_CorporateCustomersHistory_Fail(t *testing.T) {
|
|||||||
Get("/customers-corporate/history").
|
Get("/customers-corporate/history").
|
||||||
MatchParam("filter[startDate]", "2020-13-12").
|
MatchParam("filter[startDate]", "2020-13-12").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"children[startDate]": "Значение недопустимо."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"children[startDate]": "Значение недопустимо."}}`)
|
||||||
|
|
||||||
data, _, err := c.CorporateCustomersHistory(f)
|
data, _, err := c.CorporateCustomersHistory(f)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1130,7 +1128,7 @@ func TestClient_CorporateCustomersUpload_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(460).
|
Reply(460).
|
||||||
BodyString(`{"success": false, "errorMsg": "Customers are loaded with errors"}`)
|
BodyString(`{"success": false, "errorMsg": "Customers are loaded with ErrorsList"}`)
|
||||||
|
|
||||||
data, _, err := c.CorporateCustomersUpload(customers)
|
data, _, err := c.CorporateCustomersUpload(customers)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1703,7 +1701,7 @@ func TestClient_NotesNotes_Fail(t *testing.T) {
|
|||||||
Get("/customers/notes").
|
Get("/customers/notes").
|
||||||
MatchParam("filter[createdAtFrom]", "2020-13-12").
|
MatchParam("filter[createdAtFrom]", "2020-13-12").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"children[createdAtFrom]": "This value is not valid."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"children[createdAtFrom]": "This value is not valid."}}`)
|
||||||
|
|
||||||
data, _, err := c.CustomerNotes(NotesRequest{
|
data, _, err := c.CustomerNotes(NotesRequest{
|
||||||
Filter: NotesFilter{CreatedAtFrom: "2020-13-12"},
|
Filter: NotesFilter{CreatedAtFrom: "2020-13-12"},
|
||||||
@ -1803,7 +1801,7 @@ func TestClient_NotesCreateDelete_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "errors": {"customer": "Set one of the following fields: id, externalId"}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "ErrorsList": {"customer": "Set one of the following fields: id, externalId"}}`)
|
||||||
|
|
||||||
data, _, err := c.CustomerNoteCreate(note)
|
data, _, err := c.CustomerNoteCreate(note)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1875,7 +1873,7 @@ func TestClient_OrdersOrders_Fail(t *testing.T) {
|
|||||||
Get("/orders").
|
Get("/orders").
|
||||||
MatchParam("filter[attachments]", "7").
|
MatchParam("filter[attachments]", "7").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"children[attachments]": "SThis value is not valid."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"children[attachments]": "SThis value is not valid."}}`)
|
||||||
|
|
||||||
data, _, err := c.Orders(OrdersRequest{Filter: OrdersFilter{Attachments: 7}})
|
data, _, err := c.Orders(OrdersRequest{Filter: OrdersFilter{Attachments: 7}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -2166,7 +2164,7 @@ func TestClient_OrdersUpload_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(460).
|
Reply(460).
|
||||||
BodyString(`{"success": false, "errorMsg": "Orders are loaded with errors"}`)
|
BodyString(`{"success": false, "errorMsg": "Orders are loaded with ErrorsList"}`)
|
||||||
|
|
||||||
data, _, err := c.OrdersUpload(orders)
|
data, _, err := c.OrdersUpload(orders)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -2395,7 +2393,7 @@ func TestClient_OrdersHistory_Fail(t *testing.T) {
|
|||||||
Get("/orders/history").
|
Get("/orders/history").
|
||||||
MatchParam("filter[startDate]", "2020-13-12").
|
MatchParam("filter[startDate]", "2020-13-12").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"children[startDate]": "Значение недопустимо."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"children[startDate]": "Значение недопустимо."}}`)
|
||||||
|
|
||||||
data, _, err := c.OrdersHistory(OrdersHistoryRequest{Filter: OrdersHistoryFilter{StartDate: "2020-13-12"}})
|
data, _, err := c.OrdersHistory(OrdersHistoryRequest{Filter: OrdersHistoryFilter{StartDate: "2020-13-12"}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -2521,7 +2519,7 @@ func TestClient_PaymentCreateEditDelete_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "errors": {"order": "Set one of the following fields: id, externalId, number"}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "ErrorsList": {"order": "Set one of the following fields: id, externalId, number"}}`)
|
||||||
|
|
||||||
data, _, err := c.OrderPaymentCreate(f)
|
data, _, err := c.OrderPaymentCreate(f)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -2743,7 +2741,7 @@ func TestClient_TaskChange_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Task is not loaded", "errors": {"performerId": "This value should not be blank."}}`)
|
BodyString(`{"success": false, "errorMsg": "Task is not loaded", "ErrorsList": {"performerId": "This value should not be blank."}}`)
|
||||||
|
|
||||||
data, _, err := c.TaskEdit(f)
|
data, _, err := c.TaskEdit(f)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -2792,7 +2790,7 @@ func TestClient_UsersUsers_Fail(t *testing.T) {
|
|||||||
MatchParam("filter[active]", "3").
|
MatchParam("filter[active]", "3").
|
||||||
MatchParam("page", "1").
|
MatchParam("page", "1").
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "errors": {"active": "he value you selected is not a valid choice."}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the input parameters", "ErrorsList": {"active": "he value you selected is not a valid choice."}}`)
|
||||||
|
|
||||||
data, _, err := c.Users(UsersRequest{Filter: UsersFilter{Active: 3}, Page: 1})
|
data, _, err := c.Users(UsersRequest{Filter: UsersFilter{Active: 3}, Page: 1})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -3550,8 +3548,8 @@ func TestClient_Courier(t *testing.T) {
|
|||||||
cur := Courier{
|
cur := Courier{
|
||||||
Active: true,
|
Active: true,
|
||||||
Email: fmt.Sprintf("%s@example.com", RandomString(5)),
|
Email: fmt.Sprintf("%s@example.com", RandomString(5)),
|
||||||
FirstName: fmt.Sprintf("%s", RandomString(5)),
|
FirstName: RandomString(5),
|
||||||
LastName: fmt.Sprintf("%s", RandomString(5)),
|
LastName: RandomString(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
defer gock.Off()
|
defer gock.Off()
|
||||||
@ -3583,7 +3581,7 @@ func TestClient_Courier(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cur.ID = data.ID
|
cur.ID = data.ID
|
||||||
cur.Patronymic = fmt.Sprintf("%s", RandomString(5))
|
cur.Patronymic = RandomString(5)
|
||||||
|
|
||||||
jr, _ = json.Marshal(&cur)
|
jr, _ = json.Marshal(&cur)
|
||||||
|
|
||||||
@ -3629,7 +3627,7 @@ func TestClient_Courier_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(400).
|
Reply(400).
|
||||||
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "errors": {"firstName": "Specify the first name"}}`)
|
BodyString(`{"success": false, "errorMsg": "Errors in the entity format", "ErrorsList": {"firstName": "Specify the first name"}}`)
|
||||||
|
|
||||||
data, st, err := c.CourierCreate(Courier{})
|
data, st, err := c.CourierCreate(Courier{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -3644,7 +3642,7 @@ func TestClient_Courier_Fail(t *testing.T) {
|
|||||||
t.Error(successFail)
|
t.Error(successFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
cur := Courier{Patronymic: fmt.Sprintf("%s", RandomString(5))}
|
cur := Courier{Patronymic: RandomString(5)}
|
||||||
jr, _ = json.Marshal(&cur)
|
jr, _ = json.Marshal(&cur)
|
||||||
|
|
||||||
p = url.Values{
|
p = url.Values{
|
||||||
@ -5793,7 +5791,7 @@ func TestClient_CostsUpload_Fail(t *testing.T) {
|
|||||||
MatchType("url").
|
MatchType("url").
|
||||||
BodyString(p.Encode()).
|
BodyString(p.Encode()).
|
||||||
Reply(460).
|
Reply(460).
|
||||||
BodyString(`{"success": false, "errorMsg": "Costs are loaded with errors"}`)
|
BodyString(`{"success": false, "errorMsg": "Costs are loaded with ErrorsList"}`)
|
||||||
|
|
||||||
data, _, err := c.CostsUpload(costsUpload)
|
data, _, err := c.CostsUpload(costsUpload)
|
||||||
if err == nil {
|
if err == nil {
|
192
error.go
Normal file
192
error.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var missingParameterMatcher = regexp.MustCompile(`^Parameter \'([\w\]\[\_\-]+)\' is missing$`)
|
||||||
|
var (
|
||||||
|
// ErrMissingCredentials will be returned if no API key was provided to the API.
|
||||||
|
ErrMissingCredentials = NewAPIError(`apiKey is missing`)
|
||||||
|
// ErrInvalidCredentials will be returned if provided API key is invalid.
|
||||||
|
ErrInvalidCredentials = NewAPIError(`wrong "apiKey" value`)
|
||||||
|
// ErrAccessDenied will be returned in case of "Access denied" error.
|
||||||
|
ErrAccessDenied = NewAPIError("access denied")
|
||||||
|
// ErrAccountDoesNotExist will be returned if target system does not exist.
|
||||||
|
ErrAccountDoesNotExist = NewAPIError("account does not exist")
|
||||||
|
// ErrValidation will be returned in case of validation errors.
|
||||||
|
ErrValidation = NewAPIError("validation error")
|
||||||
|
// ErrMissingParameter will be returned if parameter is missing.
|
||||||
|
// Underlying error messages list will contain parameter name in the "Name" key.
|
||||||
|
ErrMissingParameter = NewAPIError("missing parameter")
|
||||||
|
// ErrGeneric will be returned if error cannot be classified as one of the errors above.
|
||||||
|
ErrGeneric = NewAPIError("API error")
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIErrorsList struct.
|
||||||
|
type APIErrorsList map[string]string
|
||||||
|
|
||||||
|
// APIError returns when an API error was occurred.
|
||||||
|
type APIError interface {
|
||||||
|
error
|
||||||
|
fmt.Stringer
|
||||||
|
withWrapped(error) APIError
|
||||||
|
withErrors(APIErrorsList) APIError
|
||||||
|
Unwrap() error
|
||||||
|
Errors() APIErrorsList
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiError struct {
|
||||||
|
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||||
|
ErrorsList APIErrorsList `json:"errors,omitempty"`
|
||||||
|
wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAPIError from the provided response data. Different error types will be returned depending on the response,
|
||||||
|
// all of them can be matched using errors.Is. APi errors will always implement APIError interface.
|
||||||
|
func CreateAPIError(dataResponse []byte) error {
|
||||||
|
a := &apiError{}
|
||||||
|
|
||||||
|
if len(dataResponse) > 0 && dataResponse[0] == '<' {
|
||||||
|
return ErrAccountDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(dataResponse, &a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var found APIError
|
||||||
|
switch a.ErrorMsg {
|
||||||
|
case `"apiKey" is missing.`:
|
||||||
|
found = ErrMissingCredentials
|
||||||
|
case `Wrong "apiKey" value.`:
|
||||||
|
found = ErrInvalidCredentials
|
||||||
|
case "Access denied.":
|
||||||
|
found = ErrAccessDenied
|
||||||
|
case "Account does not exist.":
|
||||||
|
found = ErrAccountDoesNotExist
|
||||||
|
case "Errors in the entity format":
|
||||||
|
fallthrough
|
||||||
|
case "Validation error":
|
||||||
|
found = ErrValidation
|
||||||
|
default:
|
||||||
|
if param, ok := asMissingParameterErr(a.ErrorMsg); ok {
|
||||||
|
return a.withWrapped(ErrMissingParameter).withErrors(APIErrorsList{"Name": param})
|
||||||
|
}
|
||||||
|
found = ErrGeneric
|
||||||
|
}
|
||||||
|
|
||||||
|
result := NewAPIError(a.ErrorMsg).withWrapped(found)
|
||||||
|
if len(a.ErrorsList) > 0 {
|
||||||
|
return result.withErrors(a.ErrorsList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGenericAPIError for the situations when API response cannot be processed, but response was actually received.
|
||||||
|
func CreateGenericAPIError(message string) APIError {
|
||||||
|
return NewAPIError(message).withWrapped(ErrGeneric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPIError returns API error with the provided message.
|
||||||
|
func NewAPIError(message string) APIError {
|
||||||
|
return &apiError{ErrorMsg: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsAPIError returns APIError and true if provided error is an APIError or contains wrapped APIError.
|
||||||
|
// Returns (nil, false) otherwise.
|
||||||
|
func AsAPIError(err error) (APIError, bool) {
|
||||||
|
apiErr := unwrapAPIError(err)
|
||||||
|
return apiErr, apiErr != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrapAPIError(err error) APIError {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiErr, ok := err.(APIError); ok { // nolint:errorlint
|
||||||
|
return apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, ok := err.(interface { // nolint:errorlint
|
||||||
|
Unwrap() error
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
return unwrapAPIError(wrapper.Unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// asMissingParameterErr returns true if "Parameter 'name' is missing" error message is provided.
|
||||||
|
func asMissingParameterErr(message string) (string, bool) {
|
||||||
|
matches := missingParameterMatcher.FindAllStringSubmatch(message, -1)
|
||||||
|
if len(matches) == 1 && len(matches[0]) == 2 {
|
||||||
|
return matches[0][1], true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns errorMsg field from the response.
|
||||||
|
func (e *apiError) Error() string {
|
||||||
|
return e.ErrorMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns wrapped error. It is usually one of the predefined types like ErrGeneric or ErrValidation.
|
||||||
|
// It can be used directly, but it's main purpose is to make errors matchable via errors.Is call.
|
||||||
|
func (e *apiError) Unwrap() error {
|
||||||
|
return e.wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns errors field from the response.
|
||||||
|
func (e *apiError) Errors() APIErrorsList {
|
||||||
|
return e.ErrorsList
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns string representation of an APIError.
|
||||||
|
func (e *apiError) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.Grow(256) // nolint:gomnd
|
||||||
|
sb.WriteString(fmt.Sprintf(`errorMsg: "%s"`, e.Error()))
|
||||||
|
|
||||||
|
if len(e.Errors()) > 0 {
|
||||||
|
i := 0
|
||||||
|
useIndex := true
|
||||||
|
errorList := make([]string, len(e.Errors()))
|
||||||
|
|
||||||
|
for index, errText := range e.Errors() {
|
||||||
|
if i == 0 && index == "0" {
|
||||||
|
useIndex = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if useIndex {
|
||||||
|
errorList[i] = fmt.Sprintf(`%s: "%s"`, index, errText)
|
||||||
|
} else {
|
||||||
|
errorList[i] = errText
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(", errors: [" + strings.Join(errorList, ", ") + "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// withWrapped is a wrapped setter.
|
||||||
|
func (e *apiError) withWrapped(err error) APIError {
|
||||||
|
e.wrapped = err
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// withErrors is an ErrorsList setter.
|
||||||
|
func (e *apiError) withErrors(m APIErrorsList) APIError {
|
||||||
|
e.ErrorsList = m
|
||||||
|
return e
|
||||||
|
}
|
171
error_test.go
Normal file
171
error_test.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorTest struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
suite.Run(t, new(ErrorTest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_ApiErrorsSlice() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Failed to activate module",
|
||||||
|
"errors": [
|
||||||
|
"Your account has insufficient funds to activate integration module",
|
||||||
|
"Test error"
|
||||||
|
]}`)
|
||||||
|
expected := APIErrorsList{
|
||||||
|
"0": "Your account has insufficient funds to activate integration module",
|
||||||
|
"1": "Test error",
|
||||||
|
}
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().ErrorIs(e, ErrGeneric)
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Assert().Equal(expected, apiErr.Errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_ApiErrorsMap() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Failed to activate module",
|
||||||
|
"errors": {"id": "ID must be an integer", "test": "Test error"}}`,
|
||||||
|
)
|
||||||
|
expected := APIErrorsList{
|
||||||
|
"id": "ID must be an integer",
|
||||||
|
"test": "Test error",
|
||||||
|
}
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().ErrorIs(e, ErrGeneric)
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Assert().Equal(expected, apiErr.Errors())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_APIKeyMissing() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "\"apiKey\" is missing."}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrMissingCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_APIKeyWrong() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Wrong \"apiKey\" value."}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrInvalidCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_AccessDenied() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Access denied."}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrAccessDenied)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_AccountDoesNotExist() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Account does not exist."}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrAccountDoesNotExist)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_Validation() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Errors in the entity format",
|
||||||
|
"errors": {"name": "name must be provided"}}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrValidation)
|
||||||
|
t.Assert().Equal("name must be provided", apiErr.Errors()["name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_Validation2() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Validation error",
|
||||||
|
"errors": {"name": "name must be provided"}}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrValidation)
|
||||||
|
t.Assert().Equal("name must be provided", apiErr.Errors()["name"])
|
||||||
|
t.Assert().Equal("errorMsg: \"Validation error\", errors: [name: \"name must be provided\"]", apiErr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_MissingParameter() {
|
||||||
|
b := []byte(`{"success": false,
|
||||||
|
"errorMsg": "Parameter 'item' is missing"}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
e := CreateAPIError(b)
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Require().ErrorIs(e, ErrMissingParameter)
|
||||||
|
t.Assert().Equal("item", apiErr.Errors()["Name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) Test_CreateGenericAPIError() {
|
||||||
|
e := CreateGenericAPIError("generic error message")
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Assert().ErrorIs(apiErr, ErrGeneric)
|
||||||
|
t.Assert().Equal("generic error message", e.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ErrorTest) TestFailure_HTML() {
|
||||||
|
e := CreateAPIError([]byte{'<'})
|
||||||
|
apiErr, ok := AsAPIError(e)
|
||||||
|
|
||||||
|
t.Require().NotNil(apiErr)
|
||||||
|
t.Require().True(ok)
|
||||||
|
t.Assert().ErrorIs(apiErr, ErrAccountDoesNotExist)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
// CustomersFilter type.
|
// CustomersFilter type.
|
||||||
type CustomersFilter struct {
|
type CustomersFilter struct {
|
6
go.mod
6
go.mod
@ -1,12 +1,10 @@
|
|||||||
module github.com/retailcrm/api-client-go
|
module github.com/retailcrm/api-client-go/v2
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/google/go-querystring v1.1.0
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect
|
github.com/stretchr/testify v1.7.0
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
|
|
||||||
gopkg.in/h2non/gentleman.v1 v1.0.4 // indirect
|
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
@ -8,14 +10,16 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8=
|
|
||||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
25
log.go
Normal file
25
log.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
// BasicLogger provides basic functionality for logging.
|
||||||
|
type BasicLogger interface {
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugLogger can be used to easily wrap any logger with Debugf method into the BasicLogger instance.
|
||||||
|
type DebugLogger interface {
|
||||||
|
Debugf(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type debugLoggerAdapter struct {
|
||||||
|
logger DebugLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugLoggerAdapter returns BasicLogger that calls underlying DebugLogger.Debugf.
|
||||||
|
func DebugLoggerAdapter(logger DebugLogger) BasicLogger {
|
||||||
|
return &debugLoggerAdapter{logger: logger}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf data in the log using DebugLogger.Debugf.
|
||||||
|
func (l *debugLoggerAdapter) Printf(format string, v ...interface{}) {
|
||||||
|
l.logger.Debugf(format, v...)
|
||||||
|
}
|
24
log_test.go
Normal file
24
log_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wrappedLogger struct {
|
||||||
|
lastMessage string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedLogger) Debugf(msg string, v ...interface{}) {
|
||||||
|
w.lastMessage = fmt.Sprintf(msg, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugLoggerAdapter_Printf(t *testing.T) {
|
||||||
|
wrapped := &wrappedLogger{}
|
||||||
|
logger := DebugLoggerAdapter(wrapped)
|
||||||
|
logger.Printf("Test message #%d", 1)
|
||||||
|
|
||||||
|
assert.Equal(t, "Test message #1", wrapped.lastMessage)
|
||||||
|
}
|
102
marshaling.go
Normal file
102
marshaling.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Tag) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(t.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIErrorsList) UnmarshalJSON(data []byte) error {
|
||||||
|
var i interface{}
|
||||||
|
var m APIErrorsList
|
||||||
|
if err := json.Unmarshal(data, &i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e := i.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m = make(APIErrorsList, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
m[idx] = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
m = make(APIErrorsList, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
m[strconv.Itoa(idx)] = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*a = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomFieldsList) UnmarshalJSON(data []byte) error {
|
||||||
|
var i interface{}
|
||||||
|
var m CustomFieldsList
|
||||||
|
if err := json.Unmarshal(data, &i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e := i.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m = make(CustomFieldsList, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
m[idx] = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
m = make(CustomFieldsList, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
m[strconv.Itoa(idx)] = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*l = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderPayments) UnmarshalJSON(data []byte) error {
|
||||||
|
var i interface{}
|
||||||
|
var m OrderPayments
|
||||||
|
if err := json.Unmarshal(data, &i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e := i.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m = make(OrderPayments, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
var res OrderPayment
|
||||||
|
err := unmarshalMap(val.(map[string]interface{}), &res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[idx] = res
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
m = make(OrderPayments, len(e))
|
||||||
|
for idx, val := range e {
|
||||||
|
var res OrderPayment
|
||||||
|
err := unmarshalMap(val.(map[string]interface{}), &res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m[strconv.Itoa(idx)] = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalMap(m map[string]interface{}, v interface{}) (err error) {
|
||||||
|
var data []byte
|
||||||
|
data, err = json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
69
marshaling_test.go
Normal file
69
marshaling_test.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package retailcrm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTag_MarshalJSON(t *testing.T) {
|
||||||
|
tags := []Tag{
|
||||||
|
{"first", "#3e89b6", false},
|
||||||
|
{"second", "#ffa654", false},
|
||||||
|
}
|
||||||
|
names := []byte(`["first","second"]`)
|
||||||
|
str, err := json.Marshal(tags)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(str, names) {
|
||||||
|
t.Errorf("Marshaled: %#v\nExpected: %#v\n", str, names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIErrorsList_UnmarshalJSON(t *testing.T) {
|
||||||
|
var list APIErrorsList
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`["first", "second"]`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["0"], "first")
|
||||||
|
assert.Equal(t, list["1"], "second")
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`{"a": "first", "b": "second"}`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["a"], "first")
|
||||||
|
assert.Equal(t, list["b"], "second")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomFieldsList_UnmarshalJSON(t *testing.T) {
|
||||||
|
var list CustomFieldsList
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`["first", "second"]`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["0"], "first")
|
||||||
|
assert.Equal(t, list["1"], "second")
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`{"a": "first", "b": "second"}`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["a"], "first")
|
||||||
|
assert.Equal(t, list["b"], "second")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderPayments_UnmarshalJSON(t *testing.T) {
|
||||||
|
var list OrderPayments
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`[{"id": 1}, {"id": 2}]`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["0"], OrderPayment{ID: 1})
|
||||||
|
assert.Equal(t, list["1"], OrderPayment{ID: 2})
|
||||||
|
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(`{"a": {"id": 1}, "b": {"id": 2}}`), &list))
|
||||||
|
assert.Len(t, list, 2)
|
||||||
|
assert.Equal(t, list["a"], OrderPayment{ID: 1})
|
||||||
|
assert.Equal(t, list["b"], OrderPayment{ID: 2})
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
// CustomerRequest type.
|
// CustomerRequest type.
|
||||||
type CustomerRequest struct {
|
type CustomerRequest struct {
|
||||||
@ -177,8 +177,8 @@ type ProductsPropertiesRequest struct {
|
|||||||
type DeliveryTrackingRequest struct {
|
type DeliveryTrackingRequest struct {
|
||||||
DeliveryID string `json:"deliveryId,omitempty"`
|
DeliveryID string `json:"deliveryId,omitempty"`
|
||||||
TrackNumber string `json:"trackNumber,omitempty"`
|
TrackNumber string `json:"trackNumber,omitempty"`
|
||||||
History []DeliveryHistoryRecord `json:"history,omitempty,brackets"`
|
History []DeliveryHistoryRecord `json:"history,omitempty"`
|
||||||
ExtraData map[string]string `json:"extraData,omitempty,brackets"`
|
ExtraData map[string]string `json:"extraData,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliveryShipmentsRequest type.
|
// DeliveryShipmentsRequest type.
|
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
// SuccessfulResponse type.
|
// SuccessfulResponse type.
|
||||||
type SuccessfulResponse struct {
|
type SuccessfulResponse struct {
|
||||||
@ -20,7 +20,7 @@ type OrderCreateResponse struct {
|
|||||||
// OperationResponse type.
|
// OperationResponse type.
|
||||||
type OperationResponse struct {
|
type OperationResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Errors map[string]string `json:"errors,omitempty"`
|
Errors map[string]string `json:"ErrorsList,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionResponse return available API versions.
|
// VersionResponse return available API versions.
|
||||||
@ -31,7 +31,7 @@ type VersionResponse struct {
|
|||||||
|
|
||||||
// CredentialResponse return available API methods.
|
// CredentialResponse return available API methods.
|
||||||
type CredentialResponse struct {
|
type CredentialResponse struct {
|
||||||
Success bool `json:"success,omitempty"`
|
Success bool `json:"success,omitempty"`
|
||||||
// deprecated
|
// deprecated
|
||||||
Credentials []string `json:"credentials,omitempty"`
|
Credentials []string `json:"credentials,omitempty"`
|
||||||
Scopes []string `json:"scopes,omitempty"`
|
Scopes []string `json:"scopes,omitempty"`
|
||||||
@ -409,7 +409,7 @@ type ResponseInfo struct {
|
|||||||
|
|
||||||
// MgInfo type.
|
// MgInfo type.
|
||||||
type MgInfo struct {
|
type MgInfo struct {
|
||||||
EndpointUrl string `json:"endpointUrl"`
|
EndpointURL string `json:"endpointUrl"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -19,6 +19,7 @@ type Client struct {
|
|||||||
Key string
|
Key string
|
||||||
Debug bool
|
Debug bool
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
logger BasicLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pagination type.
|
// Pagination type.
|
||||||
@ -121,41 +122,41 @@ Customer related types
|
|||||||
|
|
||||||
// Customer type.
|
// Customer type.
|
||||||
type Customer struct {
|
type Customer struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id,omitempty"`
|
||||||
ExternalID string `json:"externalId,omitempty"`
|
ExternalID string `json:"externalId,omitempty"`
|
||||||
FirstName string `json:"firstName,omitempty"`
|
FirstName string `json:"firstName,omitempty"`
|
||||||
LastName string `json:"lastName,omitempty"`
|
LastName string `json:"lastName,omitempty"`
|
||||||
Patronymic string `json:"patronymic,omitempty"`
|
Patronymic string `json:"patronymic,omitempty"`
|
||||||
Sex string `json:"sex,omitempty"`
|
Sex string `json:"sex,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Phones []Phone `json:"phones,omitempty"`
|
Phones []Phone `json:"phones,omitempty"`
|
||||||
Address *Address `json:"address,omitempty"`
|
Address *Address `json:"address,omitempty"`
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
Birthday string `json:"birthday,omitempty"`
|
Birthday string `json:"birthday,omitempty"`
|
||||||
ManagerID int `json:"managerId,omitempty"`
|
ManagerID int `json:"managerId,omitempty"`
|
||||||
Vip bool `json:"vip,omitempty"`
|
Vip bool `json:"vip,omitempty"`
|
||||||
Bad bool `json:"bad,omitempty"`
|
Bad bool `json:"bad,omitempty"`
|
||||||
Site string `json:"site,omitempty"`
|
Site string `json:"site,omitempty"`
|
||||||
Source *Source `json:"source,omitempty"`
|
Source *Source `json:"source,omitempty"`
|
||||||
Contragent *Contragent `json:"contragent,omitempty"`
|
Contragent *Contragent `json:"contragent,omitempty"`
|
||||||
PersonalDiscount float32 `json:"personalDiscount,omitempty"`
|
PersonalDiscount float32 `json:"personalDiscount,omitempty"`
|
||||||
CumulativeDiscount float32 `json:"cumulativeDiscount,omitempty"`
|
CumulativeDiscount float32 `json:"cumulativeDiscount,omitempty"`
|
||||||
DiscountCardNumber string `json:"discountCardNumber,omitempty"`
|
DiscountCardNumber string `json:"discountCardNumber,omitempty"`
|
||||||
EmailMarketingUnsubscribedAt string `json:"emailMarketingUnsubscribedAt,omitempty"`
|
EmailMarketingUnsubscribedAt string `json:"emailMarketingUnsubscribedAt,omitempty"`
|
||||||
AvgMarginSumm float32 `json:"avgMarginSumm,omitempty"`
|
AvgMarginSumm float32 `json:"avgMarginSumm,omitempty"`
|
||||||
MarginSumm float32 `json:"marginSumm,omitempty"`
|
MarginSumm float32 `json:"marginSumm,omitempty"`
|
||||||
TotalSumm float32 `json:"totalSumm,omitempty"`
|
TotalSumm float32 `json:"totalSumm,omitempty"`
|
||||||
AverageSumm float32 `json:"averageSumm,omitempty"`
|
AverageSumm float32 `json:"averageSumm,omitempty"`
|
||||||
OrdersCount int `json:"ordersCount,omitempty"`
|
OrdersCount int `json:"ordersCount,omitempty"`
|
||||||
CostSumm float32 `json:"costSumm,omitempty"`
|
CostSumm float32 `json:"costSumm,omitempty"`
|
||||||
MaturationTime int `json:"maturationTime,omitempty"`
|
MaturationTime int `json:"maturationTime,omitempty"`
|
||||||
FirstClientID string `json:"firstClientId,omitempty"`
|
FirstClientID string `json:"firstClientId,omitempty"`
|
||||||
LastClientID string `json:"lastClientId,omitempty"`
|
LastClientID string `json:"lastClientId,omitempty"`
|
||||||
BrowserID string `json:"browserId,omitempty"`
|
BrowserID string `json:"browserId,omitempty"`
|
||||||
MgCustomerID string `json:"mgCustomerId,omitempty"`
|
MgCustomerID string `json:"mgCustomerId,omitempty"`
|
||||||
PhotoURL string `json:"photoUrl,omitempty"`
|
PhotoURL string `json:"photoUrl,omitempty"`
|
||||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
CustomFields CustomFieldsList `json:"customFields,omitempty"`
|
||||||
Tags []Tag `json:"tags,omitempty"`
|
Tags []Tag `json:"tags,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CorporateCustomer type.
|
// CorporateCustomer type.
|
||||||
@ -166,7 +167,7 @@ type CorporateCustomer struct {
|
|||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
Vip bool `json:"vip,omitempty"`
|
Vip bool `json:"vip,omitempty"`
|
||||||
Bad bool `json:"bad,omitempty"`
|
Bad bool `json:"bad,omitempty"`
|
||||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
CustomFields CustomFieldsList `json:"customFields,omitempty"`
|
||||||
PersonalDiscount float32 `json:"personalDiscount,omitempty"`
|
PersonalDiscount float32 `json:"personalDiscount,omitempty"`
|
||||||
DiscountCardNumber string `json:"discountCardNumber,omitempty"`
|
DiscountCardNumber string `json:"discountCardNumber,omitempty"`
|
||||||
ManagerID int `json:"managerId,omitempty"`
|
ManagerID int `json:"managerId,omitempty"`
|
||||||
@ -217,17 +218,17 @@ type CorporateCustomerContactCustomer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Company struct {
|
type Company struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id,omitempty"`
|
||||||
IsMain bool `json:"isMain,omitempty"`
|
IsMain bool `json:"isMain,omitempty"`
|
||||||
ExternalID string `json:"externalId,omitempty"`
|
ExternalID string `json:"externalId,omitempty"`
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Brand string `json:"brand,omitempty"`
|
Brand string `json:"brand,omitempty"`
|
||||||
Site string `json:"site,omitempty"`
|
Site string `json:"site,omitempty"`
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
Contragent *Contragent `json:"contragent,omitempty"`
|
Contragent *Contragent `json:"contragent,omitempty"`
|
||||||
Address *IdentifiersPair `json:"address,omitempty"`
|
Address *IdentifiersPair `json:"address,omitempty"`
|
||||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
CustomFields CustomFieldsList `json:"customFields,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CorporateCustomerNote type.
|
// CorporateCustomerNote type.
|
||||||
@ -272,57 +273,60 @@ type CorporateCustomerHistoryRecord struct {
|
|||||||
Order related types
|
Order related types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type OrderPayments map[string]OrderPayment
|
||||||
|
type CustomFieldsList map[string]string
|
||||||
|
|
||||||
// Order type.
|
// Order type.
|
||||||
type Order struct {
|
type Order struct {
|
||||||
ID int `json:"id,omitempty"`
|
ID int `json:"id,omitempty"`
|
||||||
ExternalID string `json:"externalId,omitempty"`
|
ExternalID string `json:"externalId,omitempty"`
|
||||||
Number string `json:"number,omitempty"`
|
Number string `json:"number,omitempty"`
|
||||||
FirstName string `json:"firstName,omitempty"`
|
FirstName string `json:"firstName,omitempty"`
|
||||||
LastName string `json:"lastName,omitempty"`
|
LastName string `json:"lastName,omitempty"`
|
||||||
Patronymic string `json:"patronymic,omitempty"`
|
Patronymic string `json:"patronymic,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Phone string `json:"phone,omitempty"`
|
Phone string `json:"phone,omitempty"`
|
||||||
AdditionalPhone string `json:"additionalPhone,omitempty"`
|
AdditionalPhone string `json:"additionalPhone,omitempty"`
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
StatusUpdatedAt string `json:"statusUpdatedAt,omitempty"`
|
StatusUpdatedAt string `json:"statusUpdatedAt,omitempty"`
|
||||||
ManagerID int `json:"managerId,omitempty"`
|
ManagerID int `json:"managerId,omitempty"`
|
||||||
Mark int `json:"mark,omitempty"`
|
Mark int `json:"mark,omitempty"`
|
||||||
Call bool `json:"call,omitempty"`
|
Call bool `json:"call,omitempty"`
|
||||||
Expired bool `json:"expired,omitempty"`
|
Expired bool `json:"expired,omitempty"`
|
||||||
FromAPI bool `json:"fromApi,omitempty"`
|
FromAPI bool `json:"fromApi,omitempty"`
|
||||||
MarkDatetime string `json:"markDatetime,omitempty"`
|
MarkDatetime string `json:"markDatetime,omitempty"`
|
||||||
CustomerComment string `json:"customerComment,omitempty"`
|
CustomerComment string `json:"customerComment,omitempty"`
|
||||||
ManagerComment string `json:"managerComment,omitempty"`
|
ManagerComment string `json:"managerComment,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
StatusComment string `json:"statusComment,omitempty"`
|
StatusComment string `json:"statusComment,omitempty"`
|
||||||
FullPaidAt string `json:"fullPaidAt,omitempty"`
|
FullPaidAt string `json:"fullPaidAt,omitempty"`
|
||||||
Site string `json:"site,omitempty"`
|
Site string `json:"site,omitempty"`
|
||||||
OrderType string `json:"orderType,omitempty"`
|
OrderType string `json:"orderType,omitempty"`
|
||||||
OrderMethod string `json:"orderMethod,omitempty"`
|
OrderMethod string `json:"orderMethod,omitempty"`
|
||||||
CountryIso string `json:"countryIso,omitempty"`
|
CountryIso string `json:"countryIso,omitempty"`
|
||||||
Summ float32 `json:"summ,omitempty"`
|
Summ float32 `json:"summ,omitempty"`
|
||||||
TotalSumm float32 `json:"totalSumm,omitempty"`
|
TotalSumm float32 `json:"totalSumm,omitempty"`
|
||||||
PrepaySum float32 `json:"prepaySum,omitempty"`
|
PrepaySum float32 `json:"prepaySum,omitempty"`
|
||||||
PurchaseSumm float32 `json:"purchaseSumm,omitempty"`
|
PurchaseSumm float32 `json:"purchaseSumm,omitempty"`
|
||||||
DiscountManualAmount float32 `json:"discountManualAmount,omitempty"`
|
DiscountManualAmount float32 `json:"discountManualAmount,omitempty"`
|
||||||
DiscountManualPercent float32 `json:"discountManualPercent,omitempty"`
|
DiscountManualPercent float32 `json:"discountManualPercent,omitempty"`
|
||||||
Weight float32 `json:"weight,omitempty"`
|
Weight float32 `json:"weight,omitempty"`
|
||||||
Length int `json:"length,omitempty"`
|
Length int `json:"length,omitempty"`
|
||||||
Width int `json:"width,omitempty"`
|
Width int `json:"width,omitempty"`
|
||||||
Height int `json:"height,omitempty"`
|
Height int `json:"height,omitempty"`
|
||||||
ShipmentStore string `json:"shipmentStore,omitempty"`
|
ShipmentStore string `json:"shipmentStore,omitempty"`
|
||||||
ShipmentDate string `json:"shipmentDate,omitempty"`
|
ShipmentDate string `json:"shipmentDate,omitempty"`
|
||||||
ClientID string `json:"clientId,omitempty"`
|
ClientID string `json:"clientId,omitempty"`
|
||||||
Shipped bool `json:"shipped,omitempty"`
|
Shipped bool `json:"shipped,omitempty"`
|
||||||
UploadedToExternalStoreSystem bool `json:"uploadedToExternalStoreSystem,omitempty"`
|
UploadedToExternalStoreSystem bool `json:"uploadedToExternalStoreSystem,omitempty"`
|
||||||
Source *Source `json:"source,omitempty"`
|
Source *Source `json:"source,omitempty"`
|
||||||
Contragent *Contragent `json:"contragent,omitempty"`
|
Contragent *Contragent `json:"contragent,omitempty"`
|
||||||
Customer *Customer `json:"customer,omitempty"`
|
Customer *Customer `json:"customer,omitempty"`
|
||||||
Delivery *OrderDelivery `json:"delivery,omitempty"`
|
Delivery *OrderDelivery `json:"delivery,omitempty"`
|
||||||
Marketplace *OrderMarketplace `json:"marketplace,omitempty"`
|
Marketplace *OrderMarketplace `json:"marketplace,omitempty"`
|
||||||
Items []OrderItem `json:"items,omitempty"`
|
Items []OrderItem `json:"items,omitempty"`
|
||||||
CustomFields map[string]string `json:"customFields,omitempty"`
|
CustomFields CustomFieldsList `json:"customFields,omitempty"`
|
||||||
Payments map[string]OrderPayment `json:"payments,omitempty"`
|
Payments OrderPayments `json:"payments,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrdersStatus type.
|
// OrdersStatus type.
|
||||||
@ -378,8 +382,14 @@ type OrderDeliveryData struct {
|
|||||||
// UnmarshalJSON method.
|
// UnmarshalJSON method.
|
||||||
func (v *OrderDeliveryData) UnmarshalJSON(b []byte) error {
|
func (v *OrderDeliveryData) UnmarshalJSON(b []byte) error {
|
||||||
var additionalData map[string]interface{}
|
var additionalData map[string]interface{}
|
||||||
json.Unmarshal(b, &additionalData)
|
err := json.Unmarshal(b, &additionalData)
|
||||||
json.Unmarshal(b, &v.OrderDeliveryDataBasic)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &v.OrderDeliveryDataBasic)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
object := reflect.TypeOf(v.OrderDeliveryDataBasic)
|
object := reflect.TypeOf(v.OrderDeliveryDataBasic)
|
||||||
|
|
||||||
for i := 0; i < object.NumField(); i++ {
|
for i := 0; i < object.NumField(); i++ {
|
||||||
@ -401,7 +411,10 @@ func (v *OrderDeliveryData) UnmarshalJSON(b []byte) error {
|
|||||||
func (v OrderDeliveryData) MarshalJSON() ([]byte, error) {
|
func (v OrderDeliveryData) MarshalJSON() ([]byte, error) {
|
||||||
result := map[string]interface{}{}
|
result := map[string]interface{}{}
|
||||||
data, _ := json.Marshal(v.OrderDeliveryDataBasic)
|
data, _ := json.Marshal(v.OrderDeliveryDataBasic)
|
||||||
json.Unmarshal(data, &result)
|
err := json.Unmarshal(data, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for key, value := range v.AdditionalFields {
|
for key, value := range v.AdditionalFields {
|
||||||
result[key] = value
|
result[key] = value
|
||||||
@ -587,7 +600,7 @@ type User struct {
|
|||||||
Phone string `json:"phone,omitempty"`
|
Phone string `json:"phone,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
Groups []UserGroup `json:"groups,omitempty"`
|
Groups []UserGroup `json:"groups,omitempty"`
|
||||||
MgUserId uint64 `json:"mgUserId,omitempty"`
|
MGUserID uint64 `json:"mgUserId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserGroup type.
|
// UserGroup type.
|
||||||
@ -1054,7 +1067,7 @@ type Action struct {
|
|||||||
|
|
||||||
// MgTransport type.
|
// MgTransport type.
|
||||||
type MgTransport struct {
|
type MgTransport struct {
|
||||||
WebhookUrl string `json:"webhookUrl,omitempty"`
|
WebhookURL string `json:"webhookUrl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MgBot type.
|
// MgBot type.
|
||||||
@ -1072,7 +1085,7 @@ type CostRecord struct {
|
|||||||
DateTo string `json:"dateTo,omitempty"`
|
DateTo string `json:"dateTo,omitempty"`
|
||||||
Summ float32 `json:"summ,omitempty"`
|
Summ float32 `json:"summ,omitempty"`
|
||||||
CostItem string `json:"costItem,omitempty"`
|
CostItem string `json:"costItem,omitempty"`
|
||||||
UserId int `json:"userId,omitempty"`
|
UserID int `json:"userId,omitempty"`
|
||||||
Order *Order `json:"order,omitempty"`
|
Order *Order `json:"order,omitempty"`
|
||||||
Sites []string `json:"sites,omitempty"`
|
Sites []string `json:"sites,omitempty"`
|
||||||
}
|
}
|
||||||
@ -1089,7 +1102,7 @@ type Cost struct {
|
|||||||
CreatedAt string `json:"createdAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
CreatedBy string `json:"createdBy,omitempty"`
|
CreatedBy string `json:"createdBy,omitempty"`
|
||||||
Order *Order `json:"order,omitempty"`
|
Order *Order `json:"order,omitempty"`
|
||||||
UserId int `json:"userId,omitempty"`
|
UserID int `json:"userId,omitempty"`
|
||||||
Sites []string `json:"sites,omitempty"`
|
Sites []string `json:"sites,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package v5
|
package retailcrm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
32
v5/error.go
32
v5/error.go
@ -1,32 +0,0 @@
|
|||||||
package v5
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
// APIErrorsList struct.
|
|
||||||
type APIErrorsList map[string]string
|
|
||||||
|
|
||||||
// APIError struct.
|
|
||||||
type APIError struct {
|
|
||||||
SuccessfulResponse
|
|
||||||
ErrorMsg string `json:"errorMsg,omitempty"`
|
|
||||||
Errors APIErrorsList `json:"errors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *APIError) Error() string {
|
|
||||||
return e.ErrorMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPIError(dataResponse []byte) error {
|
|
||||||
a := &APIError{}
|
|
||||||
|
|
||||||
if len(dataResponse) > 0 && dataResponse[0] == '<' {
|
|
||||||
a.ErrorMsg = "Account does not exist."
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(dataResponse, a); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFailure_ApiErrorsSlice(t *testing.T) {
|
|
||||||
b := []byte(`{"success": false,
|
|
||||||
"errorMsg": "Failed to activate module",
|
|
||||||
"errors": [
|
|
||||||
"Your account has insufficient funds to activate integration module",
|
|
||||||
"Test error"
|
|
||||||
]}`)
|
|
||||||
expected := APIErrorsList{
|
|
||||||
"0": "Your account has insufficient funds to activate integration module",
|
|
||||||
"1": "Test error",
|
|
||||||
}
|
|
||||||
|
|
||||||
var expEr *APIError
|
|
||||||
e := NewAPIError(b)
|
|
||||||
|
|
||||||
if xerrors.As(e, &expEr) {
|
|
||||||
if eq := reflect.DeepEqual(expEr.Errors, expected); eq != true {
|
|
||||||
t.Errorf("%+v", eq)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("Error must be type of APIError: %v", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFailure_ApiErrorsMap(t *testing.T) {
|
|
||||||
b := []byte(`{"success": false,
|
|
||||||
"errorMsg": "Failed to activate module",
|
|
||||||
"errors": {"id": "ID must be an integer", "test": "Test error"}}`,
|
|
||||||
)
|
|
||||||
expected := APIErrorsList{
|
|
||||||
"id": "ID must be an integer",
|
|
||||||
"test": "Test error",
|
|
||||||
}
|
|
||||||
|
|
||||||
var expEr *APIError
|
|
||||||
e := NewAPIError(b)
|
|
||||||
|
|
||||||
if xerrors.As(e, &expEr) {
|
|
||||||
if eq := reflect.DeepEqual(expEr.Errors, expected); eq != true {
|
|
||||||
t.Errorf("%+v", eq)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("Error must be type of APIError: %v", e)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t Tag) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(t.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIErrorsList) UnmarshalJSON(data []byte) error {
|
|
||||||
var i interface{}
|
|
||||||
var m map[string]string
|
|
||||||
if err := json.Unmarshal(data, &i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e := i.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
m = make(map[string]string, len(e))
|
|
||||||
for idx, val := range e {
|
|
||||||
m[idx] = fmt.Sprint(val)
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
m = make(map[string]string, len(e))
|
|
||||||
for idx, val := range e {
|
|
||||||
m[strconv.Itoa(idx)] = fmt.Sprint(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*a = m
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package v5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTag_MarshalJSON(t *testing.T) {
|
|
||||||
tags := []Tag{
|
|
||||||
{"first", "#3e89b6", false},
|
|
||||||
{"second", "#ffa654", false},
|
|
||||||
}
|
|
||||||
names := []byte(`["first","second"]`)
|
|
||||||
str, err := json.Marshal(tags)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(str, names) {
|
|
||||||
t.Errorf("Marshaled: %#v\nExpected: %#v\n", str, names)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user