1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00

MF-1670 - Improve error handling in SDK (#1674)

* initial commit

Signed-off-by: aryan <aryangodara03@gmail.com>

* remove unused variables.

Signed-off-by: aryan <aryangodara03@gmail.com>

* removed temporarily created file.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Fix failing CI

Signed-off-by: aryan <aryangodara03@gmail.com>

* Fix thing_test failing cases.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove dead code, debug statements, and add comments.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Extract errors to separate file.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Updated things/api/http tests

Signed-off-by: aryan <aryangodara03@gmail.com>

* Created custom SDK error.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Changed to using CheckError. All tests passing.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Replace error interface with errors.SDKError interface.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Fix failing CI.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove unused sdk errors.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Change SDKError to error in internal function of sdk package.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove unused error.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove encodeError. All tests working.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Rename sdkerr vars, convert common strings to constants.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Change checkerror to take error instead of string.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove unused errors, and removed errfailedwhitelist wrap.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Removed unused errors, and remove errors.go since it only had a repeated error from errors package

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove unused errors.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Update sdk_error.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Used function to reduce code for sending and receiving requests.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Added function sendrequestandgetheadersorerror.

Signed-off-by: aryan <aryangodara03@gmail.com>

* sdk_error updated.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Updated function names to processRequest.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Made errors internal, fixed typo in http.

Signed-off-by: aryan <aryangodara03@gmail.com>

* Remove empty line.

Signed-off-by: aryan <aryangodara03@gmail.com>

* merged proceessBody and processHeaders functions in sdk.

Signed-off-by: aryan <aryangodara03@gmail.com>

* remove sendThingRequest function.

Signed-off-by: aryan <aryangodara03@gmail.com>

* changed processRequest signature

Signed-off-by: aryan <aryangodara03@gmail.com>

* changed processRequest signature, changed error names.

Signed-off-by: aryan <aryangodara03@gmail.com>

Signed-off-by: aryan <aryangodara03@gmail.com>
Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
This commit is contained in:
Aryan Godara 2022-12-15 07:24:19 -08:00 committed by GitHub
parent a48fb944c6
commit e6e9d22133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 585 additions and 1241 deletions

View File

@ -38,7 +38,6 @@ var (
errRemoveChannel = errors.New("failed to remove channel")
errCreateThing = errors.New("failed to create thing")
errDisconnectThing = errors.New("failed to disconnect thing")
errThingNotFound = errors.New("thing not found")
errCheckChannels = errors.New("failed to check if channels exists")
errConnectionChannels = errors.New("failed to check channels connections")
errUpdateCert = errors.New("failed to update cert")
@ -231,7 +230,7 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri
for _, c := range disconnect {
if err := bs.sdk.DisconnectThing(id, c, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedDisconnect) {
if errors.Contains(err, errors.ErrNotFound) {
continue
}
return ErrThings
@ -244,9 +243,6 @@ func (bs bootstrapService) UpdateConnections(ctx context.Context, token, id stri
ThingIDs: []string{id},
}
if err := bs.sdk.Connect(conIDs, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedConnect) {
return errors.ErrMalformedEntity
}
return ErrThings
}
}
@ -324,7 +320,7 @@ func (bs bootstrapService) ChangeState(ctx context.Context, token, id string, st
case Inactive:
for _, c := range cfg.MFChannels {
if err := bs.sdk.DisconnectThing(cfg.MFThing, c.ID, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedDisconnect) {
if errors.Contains(err, errors.ErrNotFound) {
continue
}
return ErrThings
@ -391,10 +387,6 @@ func (bs bootstrapService) thing(token, id string) (mfsdk.Thing, error) {
thing, err := bs.sdk.Thing(thingID, token)
if err != nil {
if errors.Contains(err, mfsdk.ErrFailedFetch) {
return mfsdk.Thing{}, errors.Wrap(errThingNotFound, errors.ErrNotFound)
}
if id != "" {
if errT := bs.sdk.DeleteThing(thingID, token); errT != nil {
err = errors.Wrap(err, errT)

95
pkg/errors/sdk_errors.go Normal file
View File

@ -0,0 +1,95 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package errors
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
const err = "error"
var (
// ErrJSONErrKey indicates response body did not contain erorr message.
errJSONKey = New("response body expected error message json key not found")
// ErrUnknown indicates that an unknown error was found in the response body.
errUnknown = New("unknown error")
)
// SDKError is an error type for Mainflux SDK.
type SDKError interface {
Error
StatusCode() int
}
var _ SDKError = (*sdkError)(nil)
type sdkError struct {
*customError
statusCode int
}
func (ce *sdkError) Error() string {
if ce == nil {
return ""
}
if ce.customError == nil {
return http.StatusText(ce.statusCode)
}
return fmt.Sprintf("Status: %s: %s", http.StatusText(ce.statusCode), ce.customError.Error())
}
func (ce *sdkError) StatusCode() int {
return ce.statusCode
}
// NewSDKError returns an SDK Error that formats as the given text.
func NewSDKError(err error) SDKError {
return &sdkError{
customError: &customError{
msg: err.Error(),
err: nil,
},
statusCode: 0,
}
}
// NewSDKErrorWithStatus returns an SDK Error setting the status code.
func NewSDKErrorWithStatus(err error, statusCode int) SDKError {
return &sdkError{
statusCode: statusCode,
customError: &customError{
msg: err.Error(),
err: nil,
},
}
}
// CheckError will check the HTTP response status code and matches it with the given status codes.
// Since multiple status codes can be valid, we can pass multiple status codes to the function.
// The function then checks for errors in the HTTP response.
func CheckError(resp *http.Response, expectedStatusCodes ...int) SDKError {
for _, expectedStatusCode := range expectedStatusCodes {
if resp.StatusCode == expectedStatusCode {
return nil
}
}
var content map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&content); err != nil {
return NewSDKErrorWithStatus(err, resp.StatusCode)
}
if msg, ok := content[err]; ok {
if v, ok := msg.(string); ok {
return NewSDKErrorWithStatus(errors.New(v), resp.StatusCode)
}
return NewSDKErrorWithStatus(errUnknown, resp.StatusCode)
}
return NewSDKErrorWithStatus(errJSONKey, resp.StatusCode)
}

View File

@ -10,16 +10,16 @@ var (
// ErrAuthorization indicates failure occurred while authorizing the entity.
ErrAuthorization = New("failed to perform authorization over the entity")
// ErrUnsupportedContentType indicates unacceptable or lack of Content-Type
// ErrUnsupportedContentType indicates unacceptable or lack of Content-Type.
ErrUnsupportedContentType = New("unsupported content type")
// ErrInvalidQueryParams indicates invalid query parameters
// ErrInvalidQueryParams indicates invalid query parameters.
ErrInvalidQueryParams = New("invalid query parameters")
// ErrNotFoundParam indicates that the parameter was not found in the query
// ErrNotFoundParam indicates that the parameter was not found in the query.
ErrNotFoundParam = New("parameter not found in the query")
// ErrMalformedEntity indicates a malformed entity specification
// ErrMalformedEntity indicates a malformed entity specification.
ErrMalformedEntity = New("malformed entity specification")
// ErrNotFound indicates a non-existent entity request.
@ -28,18 +28,18 @@ var (
// ErrConflict indicates that entity already exists.
ErrConflict = New("entity already exists")
// ErrCreateEntity indicates error in creating entity or entities
// ErrCreateEntity indicates error in creating entity or entities.
ErrCreateEntity = New("failed to create entity in the db")
// ErrViewEntity indicates error in viewing entity or entities
// ErrViewEntity indicates error in viewing entity or entities.
ErrViewEntity = New("view entity failed")
// ErrUpdateEntity indicates error in updating entity or entities
// ErrUpdateEntity indicates error in updating entity or entities.
ErrUpdateEntity = New("update entity failed")
// ErrRemoveEntity indicates error in removing entity
// ErrRemoveEntity indicates error in removing entity.
ErrRemoveEntity = New("failed to remove entity")
// ErrScanMetadata indicates problem with metadata in db
// ErrScanMetadata indicates problem with metadata in db.
ErrScanMetadata = New("failed to scan metadata in db")
)

View File

@ -4,10 +4,8 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -46,115 +44,65 @@ type ConfigUpdateCertReq struct {
CACert string `json:"ca_cert"`
}
func (sdk mfSDK) AddBootstrap(token string, cfg BootstrapConfig) (string, error) {
func (sdk mfSDK) AddBootstrap(token string, cfg BootstrapConfig) (string, errors.SDKError) {
data, err := json.Marshal(cfg)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.bootstrapURL, configsEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusOK, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
id := strings.TrimPrefix(resp.Header.Get("Location"), "/things/configs/")
id := strings.TrimPrefix(headers.Get("Location"), "/things/configs/")
return id, nil
}
func (sdk mfSDK) Whitelist(token string, cfg BootstrapConfig) error {
func (sdk mfSDK) Whitelist(token string, cfg BootstrapConfig) errors.SDKError {
data, err := json.Marshal(BootstrapConfig{State: cfg.State})
if err != nil {
return errors.Wrap(ErrFailedWhitelist, err)
return errors.NewSDKError(err)
}
if cfg.MFThing == "" {
return ErrFailedWhitelist
return errors.NewSDKError(errors.ErrNotFoundParam)
}
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, whitelistEndpoint, cfg.MFThing)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return errors.Wrap(ErrFailedWhitelist, err)
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return errors.Wrap(ErrFailedWhitelist, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedWhitelist, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusCreated, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) ViewBootstrap(token, id string) (BootstrapConfig, error) {
func (sdk mfSDK) ViewBootstrap(token, id string) (BootstrapConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, configsEndpoint, id)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return BootstrapConfig{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return BootstrapConfig{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BootstrapConfig{}, err
}
if resp.StatusCode != http.StatusOK {
return BootstrapConfig{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var bc BootstrapConfig
if err := json.Unmarshal(body, &bc); err != nil {
return BootstrapConfig{}, err
return BootstrapConfig{}, errors.NewSDKError(err)
}
return bc, nil
}
func (sdk mfSDK) UpdateBootstrap(token string, cfg BootstrapConfig) error {
func (sdk mfSDK) UpdateBootstrap(token string, cfg BootstrapConfig) errors.SDKError {
data, err := json.Marshal(cfg)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, configsEndpoint, cfg.MFThing)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) UpdateBootstrapCerts(token, id, clientCert, clientKey, ca string) error {
func (sdk mfSDK) UpdateBootstrapCerts(token, id, clientCert, clientKey, ca string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, bootstrapCertsEndpoint, id)
request := ConfigUpdateCertReq{
ClientCert: clientCert,
@ -164,69 +112,29 @@ func (sdk mfSDK) UpdateBootstrapCerts(token, id, clientCert, clientKey, ca strin
data, err := json.Marshal(request)
if err != nil {
return errors.Wrap(ErrFailedCertUpdate, err)
}
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(data))
if err != nil {
return err
return errors.NewSDKError(err)
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedCertUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPatch, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) RemoveBootstrap(token, id string) error {
func (sdk mfSDK) RemoveBootstrap(token, id string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, configsEndpoint, id)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}
func (sdk mfSDK) Bootstrap(externalKey, externalID string) (BootstrapConfig, error) {
func (sdk mfSDK) Bootstrap(externalKey, externalID string) (BootstrapConfig, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.bootstrapURL, bootstrapEndpoint, externalID)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, externalKey, string(CTJSON), nil, http.StatusOK)
if err != nil {
return BootstrapConfig{}, err
}
resp, err := sdk.sendRequest(req, externalKey, string(CTJSON))
if err != nil {
return BootstrapConfig{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return BootstrapConfig{}, err
}
if resp.StatusCode != http.StatusOK {
return BootstrapConfig{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var bc BootstrapConfig
if err := json.Unmarshal(body, &bc); err != nil {
return BootstrapConfig{}, err
return BootstrapConfig{}, errors.NewSDKError(err)
}
return bc, nil

View File

@ -1,10 +1,12 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/mainflux/mainflux/pkg/errors"
@ -19,8 +21,7 @@ type Cert struct {
ClientCert string `json:"client_cert,omitempty"`
}
func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, error) {
var c Cert
func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, errors.SDKError) {
r := certReq{
ThingID: thingID,
KeyBits: keyBits,
@ -29,62 +30,58 @@ func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token st
}
d, err := json.Marshal(r)
if err != nil {
return Cert{}, err
return Cert{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.certsURL, certsEndpoint)
res, err := request(http.MethodPost, token, url, d)
resp, err := request(http.MethodPost, token, url, d)
if err != nil {
return Cert{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
if err := errors.CheckError(resp, http.StatusOK); err != nil {
return Cert{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return Cert{}, ErrCerts
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
println(err.Error())
return Cert{}, err
}
if err := json.Unmarshal(body, &c); err != nil {
return Cert{}, err
var c Cert
if err := json.NewDecoder(resp.Body).Decode(&c); err != nil {
return Cert{}, errors.NewSDKError(err)
}
return c, nil
}
func (sdk mfSDK) RemoveCert(id, token string) error {
res, err := request(http.MethodDelete, token, fmt.Sprintf("%s/%s", sdk.certsURL, id), nil)
if res != nil {
res.Body.Close()
func (sdk mfSDK) RemoveCert(id, token string) errors.SDKError {
resp, err := request(http.MethodDelete, token, fmt.Sprintf("%s/%s", sdk.certsURL, id), nil)
if resp != nil {
resp.Body.Close()
}
if err != nil {
return err
return errors.NewSDKError(err)
}
switch res.StatusCode {
case http.StatusNoContent:
return nil
switch resp.StatusCode {
case http.StatusForbidden:
return errors.ErrAuthorization
return errors.NewSDKError(errors.ErrAuthorization)
default:
return ErrCertsRemove
return errors.CheckError(resp, http.StatusNoContent)
}
}
func (sdk mfSDK) RevokeCert(thingID, certID string, token string) error {
func (sdk mfSDK) RevokeCert(thingID, certID string, token string) errors.SDKError {
panic("not implemented")
}
func request(method, jwt, url string, data []byte) (*http.Response, error) {
func request(method, jwt, url string, data []byte) (*http.Response, errors.SDKError) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
if err != nil {
return nil, err
return nil, errors.NewSDKError(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", jwt)
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
return nil, err
return nil, errors.NewSDKError(err)
}
return res, nil

View File

@ -4,10 +4,8 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -16,198 +14,111 @@ import (
const channelsEndpoint = "channels"
func (sdk mfSDK) CreateChannel(c Channel, token string) (string, error) {
func (sdk mfSDK) CreateChannel(c Channel, token string) (string, errors.SDKError) {
data, err := json.Marshal(c)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.thingsURL, channelsEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusCreated {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
id := strings.TrimPrefix(resp.Header.Get("Location"), fmt.Sprintf("/%s/", channelsEndpoint))
id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", channelsEndpoint))
return id, nil
}
func (sdk mfSDK) CreateChannels(chs []Channel, token string) ([]Channel, error) {
func (sdk mfSDK) CreateChannels(chs []Channel, token string) ([]Channel, errors.SDKError) {
data, err := json.Marshal(chs)
if err != nil {
return []Channel{}, err
return []Channel{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, channelsEndpoint, "bulk")
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return []Channel{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return []Channel{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return []Channel{}, errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return []Channel{}, err
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return []Channel{}, sdkerr
}
var ccr createChannelsRes
if err := json.Unmarshal(body, &ccr); err != nil {
return []Channel{}, err
return []Channel{}, errors.NewSDKError(err)
}
return ccr.Channels, nil
}
func (sdk mfSDK) Channels(token string, pm PageMetadata) (ChannelsPage, error) {
url, err := sdk.withQueryParams(sdk.thingsURL, channelsEndpoint, pm)
if err != nil {
return ChannelsPage{}, err
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return ChannelsPage{}, err
func (sdk mfSDK) Channels(token string, pm PageMetadata) (ChannelsPage, errors.SDKError) {
var url string
var err error
if url, err = sdk.withQueryParams(sdk.thingsURL, channelsEndpoint, pm); err != nil {
return ChannelsPage{}, errors.NewSDKError(err)
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return ChannelsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ChannelsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return ChannelsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
_, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if sdkerr != nil {
return ChannelsPage{}, sdkerr
}
var cp ChannelsPage
if err := json.Unmarshal(body, &cp); err != nil {
return ChannelsPage{}, err
if err = json.Unmarshal(body, &cp); err != nil {
return ChannelsPage{}, errors.NewSDKError(err)
}
return cp, nil
}
func (sdk mfSDK) ChannelsByThing(token, thingID string, offset, limit uint64, disconn bool) (ChannelsPage, error) {
func (sdk mfSDK) ChannelsByThing(token, thingID string, offset, limit uint64, disconn bool) (ChannelsPage, errors.SDKError) {
url := fmt.Sprintf("%s/things/%s/channels?offset=%d&limit=%d&disconnected=%t", sdk.thingsURL, thingID, offset, limit, disconn)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return ChannelsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return ChannelsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ChannelsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return ChannelsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var cp ChannelsPage
if err := json.Unmarshal(body, &cp); err != nil {
return ChannelsPage{}, err
return ChannelsPage{}, errors.NewSDKError(err)
}
return cp, nil
}
func (sdk mfSDK) Channel(id, token string) (Channel, error) {
func (sdk mfSDK) Channel(id, token string) (Channel, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, channelsEndpoint, id)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return Channel{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return Channel{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Channel{}, err
}
if resp.StatusCode != http.StatusOK {
return Channel{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var c Channel
if err := json.Unmarshal(body, &c); err != nil {
return Channel{}, err
return Channel{}, errors.NewSDKError(err)
}
return c, nil
}
func (sdk mfSDK) UpdateChannel(c Channel, token string) error {
func (sdk mfSDK) UpdateChannel(c Channel, token string) errors.SDKError {
data, err := json.Marshal(c)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, channelsEndpoint, c.ID)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) DeleteChannel(id, token string) error {
func (sdk mfSDK) DeleteChannel(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, channelsEndpoint, id)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}

View File

@ -11,6 +11,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/pkg/errors"
sdk "github.com/mainflux/mainflux/pkg/sdk/go"
)
@ -41,7 +43,7 @@ func TestCreateChannel(t *testing.T) {
desc string
channel sdk.Channel
token string
err error
err errors.SDKError
empty bool
}{
{
@ -55,14 +57,14 @@ func TestCreateChannel(t *testing.T) {
desc: "create new channel with empty token",
channel: ch1,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
empty: true,
},
{
desc: "create new channel with invalid token",
channel: ch1,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
empty: true,
},
{
@ -83,7 +85,7 @@ func TestCreateChannel(t *testing.T) {
desc: "create a new channel with wrong external UUID",
channel: chWrongExtID,
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidIDFormat, http.StatusBadRequest),
empty: true,
},
}
@ -117,7 +119,7 @@ func TestCreateChannels(t *testing.T) {
desc string
channels []sdk.Channel
token string
err error
err errors.SDKError
res []sdk.Channel
}{
{
@ -131,21 +133,21 @@ func TestCreateChannels(t *testing.T) {
desc: "create new channels with empty channels",
channels: []sdk.Channel{},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrEmptyList, http.StatusBadRequest),
res: []sdk.Channel{},
},
{
desc: "create new channels with empty token",
channels: channels,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
res: []sdk.Channel{},
},
{
desc: "create new channels with invalid token",
channels: channels,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
res: []sdk.Channel{},
},
}
@ -177,7 +179,7 @@ func TestChannel(t *testing.T) {
desc string
chanID string
token string
err error
err errors.SDKError
response sdk.Channel
}{
{
@ -191,14 +193,14 @@ func TestChannel(t *testing.T) {
desc: "get non-existent channel",
chanID: "43",
token: token,
err: createError(sdk.ErrFailedFetch, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
response: sdk.Channel{},
},
{
desc: "get channel with invalid token",
chanID: id,
token: "",
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
response: sdk.Channel{},
},
}
@ -237,7 +239,7 @@ func TestChannels(t *testing.T) {
offset uint64
limit uint64
name string
err error
err errors.SDKError
response []sdk.Channel
metadata map[string]interface{}
}{
@ -255,7 +257,7 @@ func TestChannels(t *testing.T) {
token: wrongValue,
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: nil,
metadata: make(map[string]interface{}),
},
@ -264,7 +266,7 @@ func TestChannels(t *testing.T) {
token: "",
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
response: nil,
metadata: make(map[string]interface{}),
},
@ -273,7 +275,7 @@ func TestChannels(t *testing.T) {
token: token,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
metadata: make(map[string]interface{}),
},
@ -282,7 +284,7 @@ func TestChannels(t *testing.T) {
token: token,
offset: offset,
limit: 110,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
metadata: make(map[string]interface{}),
},
@ -358,7 +360,7 @@ func TestChannelsByThing(t *testing.T) {
offset uint64
limit uint64
disconnected bool
err error
err errors.SDKError
response []sdk.Channel
}{
{
@ -376,7 +378,7 @@ func TestChannelsByThing(t *testing.T) {
token: wrongValue,
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: nil,
},
{
@ -385,7 +387,7 @@ func TestChannelsByThing(t *testing.T) {
token: "",
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
response: nil,
},
{
@ -394,7 +396,7 @@ func TestChannelsByThing(t *testing.T) {
token: token,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -403,7 +405,7 @@ func TestChannelsByThing(t *testing.T) {
token: token,
offset: offset,
limit: 110,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -421,7 +423,7 @@ func TestChannelsByThing(t *testing.T) {
token: wrongValue,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -461,7 +463,7 @@ func TestUpdateChannel(t *testing.T) {
desc string
channel sdk.Channel
token string
err error
err errors.SDKError
}{
{
desc: "update existing channel",
@ -473,25 +475,25 @@ func TestUpdateChannel(t *testing.T) {
desc: "update non-existing channel",
channel: sdk.Channel{ID: "0", Name: "test2"},
token: token,
err: createError(sdk.ErrFailedUpdate, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "update channel with invalid id",
channel: sdk.Channel{ID: "", Name: "test2"},
token: token,
err: createError(sdk.ErrFailedUpdate, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "update channel with invalid token",
channel: sdk.Channel{ID: id, Name: "test2"},
token: wrongValue,
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "update channel with empty token",
channel: sdk.Channel{ID: id, Name: "test2"},
token: "",
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
}
@ -519,13 +521,13 @@ func TestDeleteChannel(t *testing.T) {
desc string
chanID string
token string
err error
err errors.SDKError
}{
{
desc: "delete channel with invalid token",
chanID: id,
token: wrongValue,
err: createError(sdk.ErrFailedRemoval, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "delete non-existing channel",
@ -537,13 +539,13 @@ func TestDeleteChannel(t *testing.T) {
desc: "delete channel with invalid id",
chanID: "",
token: token,
err: createError(sdk.ErrFailedRemoval, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "delete channel with empty token",
chanID: id,
token: "",
err: createError(sdk.ErrFailedRemoval, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
{
desc: "delete existing channel",

View File

@ -4,10 +4,8 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -22,52 +20,29 @@ const (
MinLevel = uint64(1)
)
func (sdk mfSDK) CreateGroup(g Group, token string) (string, error) {
func (sdk mfSDK) CreateGroup(g Group, token string) (string, errors.SDKError) {
data, err := json.Marshal(g)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.authURL, groupsEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
id := strings.TrimPrefix(resp.Header.Get("Location"), fmt.Sprintf("/%s/", groupsEndpoint))
id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", groupsEndpoint))
return id, nil
}
func (sdk mfSDK) DeleteGroup(id, token string) error {
func (sdk mfSDK) DeleteGroup(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.authURL, groupsEndpoint, id)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}
func (sdk mfSDK) Assign(memberIDs []string, memberType, groupID string, token string) error {
func (sdk mfSDK) Assign(memberIDs []string, memberType, groupID string, token string) errors.SDKError {
var ids []string
url := fmt.Sprintf("%s/%s/%s/members", sdk.authURL, groupsEndpoint, groupID)
ids = append(ids, memberIDs...)
@ -78,27 +53,14 @@ func (sdk mfSDK) Assign(memberIDs []string, memberType, groupID string, token st
data, err := json.Marshal(assignReq)
if err != nil {
return err
return errors.NewSDKError(err)
}
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrMemberAdd, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) Unassign(token, groupID string, memberIDs ...string) error {
func (sdk mfSDK) Unassign(token, groupID string, memberIDs ...string) errors.SDKError {
var ids []string
url := fmt.Sprintf("%s/%s/%s/members", sdk.authURL, groupsEndpoint, groupID)
ids = append(ids, memberIDs...)
@ -108,60 +70,33 @@ func (sdk mfSDK) Unassign(token, groupID string, memberIDs ...string) error {
data, err := json.Marshal(assignReq)
if err != nil {
return err
return errors.NewSDKError(err)
}
req, err := http.NewRequest(http.MethodDelete, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), data, http.StatusNoContent)
return sdkerr
}
func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (MembersPage, error) {
func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (MembersPage, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/members?offset=%d&limit=%d&", sdk.authURL, groupsEndpoint, groupID, offset, limit)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return MembersPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return MembersPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return MembersPage{}, err
}
if resp.StatusCode != http.StatusOK {
return MembersPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp MembersPage
if err := json.Unmarshal(body, &tp); err != nil {
return MembersPage{}, err
return MembersPage{}, errors.NewSDKError(err)
}
return tp, nil
}
func (sdk mfSDK) Groups(meta PageMetadata, token string) (GroupsPage, error) {
func (sdk mfSDK) Groups(meta PageMetadata, token string) (GroupsPage, errors.SDKError) {
u, err := url.Parse(sdk.authURL)
if err != nil {
return GroupsPage{}, err
return GroupsPage{}, errors.NewSDKError(err)
}
u.Path = groupsEndpoint
q := u.Query()
@ -182,123 +117,66 @@ func (sdk mfSDK) Groups(meta PageMetadata, token string) (GroupsPage, error) {
return sdk.getGroups(token, u.String())
}
func (sdk mfSDK) Parents(id string, offset, limit uint64, token string) (GroupsPage, error) {
func (sdk mfSDK) Parents(id string, offset, limit uint64, token string) (GroupsPage, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/parents?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, MaxLevel)
return sdk.getGroups(token, url)
}
func (sdk mfSDK) Children(id string, offset, limit uint64, token string) (GroupsPage, error) {
func (sdk mfSDK) Children(id string, offset, limit uint64, token string) (GroupsPage, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/children?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, MaxLevel)
return sdk.getGroups(token, url)
}
func (sdk mfSDK) getGroups(token, url string) (GroupsPage, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
func (sdk mfSDK) getGroups(token, url string) (GroupsPage, errors.SDKError) {
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return GroupsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return GroupsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return GroupsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return GroupsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp GroupsPage
if err := json.Unmarshal(body, &tp); err != nil {
return GroupsPage{}, err
return GroupsPage{}, errors.NewSDKError(err)
}
return tp, nil
}
func (sdk mfSDK) Group(id, token string) (Group, error) {
func (sdk mfSDK) Group(id, token string) (Group, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.authURL, groupsEndpoint, id)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return Group{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return Group{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Group{}, err
}
if resp.StatusCode != http.StatusOK {
return Group{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var t Group
if err := json.Unmarshal(body, &t); err != nil {
return Group{}, err
return Group{}, errors.NewSDKError(err)
}
return t, nil
}
func (sdk mfSDK) UpdateGroup(t Group, token string) error {
func (sdk mfSDK) UpdateGroup(t Group, token string) errors.SDKError {
data, err := json.Marshal(t)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.authURL, groupsEndpoint, t.ID)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return err
}
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusOK)
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
return sdkerr
}
func (sdk mfSDK) Memberships(memberID, token string, offset, limit uint64) (GroupsPage, error) {
func (sdk mfSDK) Memberships(memberID, token string, offset, limit uint64) (GroupsPage, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s/groups?offset=%d&limit=%d&", sdk.authURL, membersEndpoint, memberID, offset, limit)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return GroupsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return GroupsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return GroupsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return GroupsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp GroupsPage
if err := json.Unmarshal(body, &tp); err != nil {
return GroupsPage{}, err
return GroupsPage{}, errors.NewSDKError(err)
}
return tp, nil

View File

@ -6,34 +6,28 @@ package sdk
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/pkg/errors"
)
func (sdk mfSDK) Health() (mainflux.HealthInfo, error) {
func (sdk mfSDK) Health() (mainflux.HealthInfo, errors.SDKError) {
url := fmt.Sprintf("%s/health", sdk.thingsURL)
resp, err := sdk.client.Get(url)
if err != nil {
return mainflux.HealthInfo{}, err
return mainflux.HealthInfo{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
if err := errors.CheckError(resp, http.StatusOK); err != nil {
return mainflux.HealthInfo{}, err
}
if resp.StatusCode != http.StatusOK {
return mainflux.HealthInfo{}, errors.Wrap(ErrFetchHealth, errors.New(resp.Status))
}
var h mainflux.HealthInfo
if err := json.Unmarshal(body, &h); err != nil {
return mainflux.HealthInfo{}, err
if err := json.NewDecoder(resp.Body).Decode(&h); err != nil {
return mainflux.HealthInfo{}, errors.NewSDKError(err)
}
return h, nil

View File

@ -1,3 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
@ -5,6 +8,7 @@ import (
"testing"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/pkg/errors"
sdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/stretchr/testify/assert"
)
@ -28,7 +32,7 @@ func TestHealth(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
cases := map[string]struct {
empty bool
err error
err errors.SDKError
}{
"get things service health check": {
empty: false,

View File

@ -1,10 +1,11 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
@ -27,87 +28,44 @@ const (
APIKey
)
func (sdk mfSDK) Issue(token string, d time.Duration) (KeyRes, error) {
func (sdk mfSDK) Issue(token string, d time.Duration) (KeyRes, errors.SDKError) {
datareq := keyReq{Type: APIKey, Duration: d}
data, err := json.Marshal(datareq)
if err != nil {
return KeyRes{}, err
return KeyRes{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.authURL, keysEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return KeyRes{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return KeyRes{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return KeyRes{}, err
}
if resp.StatusCode != http.StatusCreated {
return KeyRes{}, errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return KeyRes{}, sdkerr
}
var key KeyRes
if err := json.Unmarshal(body, &key); err != nil {
return KeyRes{}, err
return KeyRes{}, errors.NewSDKError(err)
}
return key, nil
}
func (sdk mfSDK) Revoke(id, token string) error {
func (sdk mfSDK) Revoke(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.authURL, keysEndpoint, id)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}
func (sdk mfSDK) RetrieveKey(id, token string) (retrieveKeyRes, error) {
func (sdk mfSDK) RetrieveKey(id, token string) (retrieveKeyRes, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.authURL, keysEndpoint, id)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return retrieveKeyRes{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return retrieveKeyRes{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return retrieveKeyRes{}, err
}
if resp.StatusCode != http.StatusOK {
return retrieveKeyRes{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var key retrieveKeyRes
if err := json.Unmarshal(body, &key); err != nil {
return retrieveKeyRes{}, err
return retrieveKeyRes{}, errors.NewSDKError(err)
}
return key, nil

View File

@ -6,14 +6,14 @@ package sdk
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/pkg/errors"
)
func (sdk mfSDK) SendMessage(chanName, msg, key string) error {
func (sdk mfSDK) SendMessage(chanName, msg, key string) errors.SDKError {
chanNameParts := strings.SplitN(chanName, ".", 2)
chanID := chanNameParts[0]
subtopicPart := ""
@ -23,24 +23,11 @@ func (sdk mfSDK) SendMessage(chanName, msg, key string) error {
url := fmt.Sprintf("%s/channels/%s/messages/%s", sdk.httpAdapterURL, chanID, subtopicPart)
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(msg))
if err != nil {
return err
}
resp, err := sdk.sendThingRequest(req, key, string(sdk.msgContentType))
if err != nil {
return err
}
if resp.StatusCode != http.StatusAccepted {
return errors.Wrap(ErrFailedPublish, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodPost, url, apiutil.ThingPrefix+key, string(CTJSON), []byte(msg), http.StatusAccepted)
return err
}
func (sdk mfSDK) ReadMessages(chanName, token string) (MessagesPage, error) {
func (sdk mfSDK) ReadMessages(chanName, token string) (MessagesPage, errors.SDKError) {
chanNameParts := strings.SplitN(chanName, ".", 2)
chanID := chanNameParts[0]
subtopicPart := ""
@ -49,39 +36,25 @@ func (sdk mfSDK) ReadMessages(chanName, token string) (MessagesPage, error) {
}
url := fmt.Sprintf("%s/channels/%s/messages%s", sdk.readerURL, chanID, subtopicPart)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(sdk.msgContentType), nil, http.StatusOK)
if err != nil {
return MessagesPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(sdk.msgContentType))
if err != nil {
return MessagesPage{}, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return MessagesPage{}, err
}
if resp.StatusCode != http.StatusOK {
return MessagesPage{}, errors.Wrap(ErrFailedRead, errors.New(resp.Status))
}
var mp MessagesPage
if err := json.Unmarshal(body, &mp); err != nil {
return MessagesPage{}, err
return MessagesPage{}, errors.NewSDKError(err)
}
return mp, nil
}
func (sdk mfSDK) SetContentType(ct ContentType) error {
func (sdk *mfSDK) SetContentType(ct ContentType) errors.SDKError {
if ct != CTJSON && ct != CTJSONSenML && ct != CTBinary {
return ErrInvalidContentType
return errors.NewSDKError(errors.ErrUnsupportedContentType)
}
sdk.msgContentType = ct
return nil
}

View File

@ -13,12 +13,16 @@ import (
adapter "github.com/mainflux/mainflux/http"
"github.com/mainflux/mainflux/http/api"
"github.com/mainflux/mainflux/http/mocks"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
sdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
)
const eof = "EOF"
func newMessageService(cc mainflux.ThingsServiceClient) adapter.Service {
pub := mocks.NewPublisher()
return adapter.New(pub, cc)
@ -51,7 +55,7 @@ func TestSendMessage(t *testing.T) {
chanID string
msg string
auth string
err error
err errors.SDKError
}{
"publish message": {
chanID: chanID,
@ -63,13 +67,13 @@ func TestSendMessage(t *testing.T) {
chanID: chanID,
msg: msg,
auth: "",
err: createError(sdk.ErrFailedPublish, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
"publish message with invalid authorization token": {
chanID: chanID,
msg: msg,
auth: invalidToken,
err: createError(sdk.ErrFailedPublish, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.New(eof), http.StatusUnauthorized),
},
"publish message with wrong content type": {
chanID: chanID,
@ -81,18 +85,22 @@ func TestSendMessage(t *testing.T) {
chanID: "",
msg: msg,
auth: atoken,
err: createError(sdk.ErrFailedPublish, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest),
},
"publish message unable to authorize": {
chanID: chanID,
msg: msg,
auth: "invalid-token",
err: createError(sdk.ErrFailedPublish, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.New(eof), http.StatusUnauthorized),
},
}
for desc, tc := range cases {
err := mainfluxSDK.SendMessage(tc.chanID, tc.msg, tc.auth)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", desc, tc.err, err))
if tc.err == nil {
assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s", desc, err))
} else {
assert.Equal(t, tc.err.Error(), err.Error(), fmt.Sprintf("%s: expected error %s, got %s", desc, err, tc.err))
}
}
}
@ -115,7 +123,7 @@ func TestSetContentType(t *testing.T) {
cases := []struct {
desc string
cType sdk.ContentType
err error
err errors.SDKError
}{
{
desc: "set senml+json content type",
@ -125,7 +133,7 @@ func TestSetContentType(t *testing.T) {
{
desc: "set invalid content type",
cType: "invalid",
err: sdk.ErrInvalidContentType,
err: errors.NewSDKError(errors.ErrUnsupportedContentType),
},
}
for _, tc := range cases {

View File

@ -4,17 +4,20 @@
package sdk
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/pkg/errors"
)
const (
@ -28,54 +31,6 @@ const (
CTBinary ContentType = "application/octet-stream"
)
var (
// ErrFailedCreation indicates that entity creation failed.
ErrFailedCreation = errors.New("failed to create entity")
// ErrFailedUpdate indicates that entity update failed.
ErrFailedUpdate = errors.New("failed to update entity")
// ErrFailedFetch indicates that fetching of entity data failed.
ErrFailedFetch = errors.New("failed to fetch entity")
// ErrFailedRemoval indicates that entity removal failed.
ErrFailedRemoval = errors.New("failed to remove entity")
// ErrFailedConnect indicates that connecting thing to channel failed.
ErrFailedConnect = errors.New("failed to connect thing to channel")
// ErrFailedDisconnect indicates that disconnecting thing from a channel failed.
ErrFailedDisconnect = errors.New("failed to disconnect thing from channel")
// ErrFailedPublish indicates that publishing message failed.
ErrFailedPublish = errors.New("failed to publish message")
// ErrFailedRead indicates that read messages failed.
ErrFailedRead = errors.New("failed to read messages")
// ErrInvalidContentType indicates that non-existent message content type
// was passed.
ErrInvalidContentType = errors.New("Unknown Content Type")
// ErrFetchHealth indicates that fetching of health check failed.
ErrFetchHealth = errors.New("failed to fetch health check")
// ErrFailedWhitelist failed to whitelist configs
ErrFailedWhitelist = errors.New("failed to whitelist")
// ErrCerts indicates error fetching certificates.
ErrCerts = errors.New("failed to fetch certs data")
// ErrCertsRemove indicates failure while cleaning up from the Certs service.
ErrCertsRemove = errors.New("failed to remove certificate")
// ErrFailedCertUpdate failed to update certs in bootstrap config
ErrFailedCertUpdate = errors.New("failed to update certs in bootstrap config")
// ErrMemberAdd failed to add member to a group.
ErrMemberAdd = errors.New("failed to add member to group")
)
// ContentType represents all possible content types.
type ContentType string
@ -137,165 +92,165 @@ type Key struct {
// SDK contains Mainflux API.
type SDK interface {
// CreateUser registers mainflux user.
CreateUser(token string, user User) (string, error)
CreateUser(token string, user User) (string, errors.SDKError)
// User returns user object by id.
User(token, id string) (User, error)
User(token, id string) (User, errors.SDKError)
// Users returns list of users.
Users(token string, pm PageMetadata) (UsersPage, error)
Users(token string, pm PageMetadata) (UsersPage, errors.SDKError)
// CreateToken receives credentials and returns user token.
CreateToken(user User) (string, error)
CreateToken(user User) (string, errors.SDKError)
// UpdateUser updates existing user.
UpdateUser(user User, token string) error
UpdateUser(user User, token string) errors.SDKError
// UpdatePassword updates user password.
UpdatePassword(oldPass, newPass, token string) error
UpdatePassword(oldPass, newPass, token string) errors.SDKError
// EnableUser changes the status of the user to enabled.
EnableUser(id, token string) error
EnableUser(id, token string) errors.SDKError
// DisableUser changes the status of the user to disabled.
DisableUser(id, token string) error
DisableUser(id, token string) errors.SDKError
// CreateThing registers new thing and returns its id.
CreateThing(thing Thing, token string) (string, error)
CreateThing(thing Thing, token string) (string, errors.SDKError)
// CreateThings registers new things and returns their ids.
CreateThings(things []Thing, token string) ([]Thing, error)
CreateThings(things []Thing, token string) ([]Thing, errors.SDKError)
// Things returns page of things.
Things(token string, pm PageMetadata) (ThingsPage, error)
Things(token string, pm PageMetadata) (ThingsPage, errors.SDKError)
// ThingsByChannel returns page of things that are connected or not connected
// to specified channel.
ThingsByChannel(token, chanID string, offset, limit uint64, disconnected bool) (ThingsPage, error)
ThingsByChannel(token, chanID string, offset, limit uint64, disconnected bool) (ThingsPage, errors.SDKError)
// Thing returns thing object by id.
Thing(id, token string) (Thing, error)
Thing(id, token string) (Thing, errors.SDKError)
// UpdateThing updates existing thing.
UpdateThing(thing Thing, token string) error
UpdateThing(thing Thing, token string) errors.SDKError
// DeleteThing removes existing thing.
DeleteThing(id, token string) error
DeleteThing(id, token string) errors.SDKError
// IdentifyThing validates thing's key and returns its ID
IdentifyThing(key string) (string, error)
IdentifyThing(key string) (string, errors.SDKError)
// CreateGroup creates new group and returns its id.
CreateGroup(group Group, token string) (string, error)
CreateGroup(group Group, token string) (string, errors.SDKError)
// DeleteGroup deletes users group.
DeleteGroup(id, token string) error
DeleteGroup(id, token string) errors.SDKError
// Groups returns page of groups.
Groups(meta PageMetadata, token string) (GroupsPage, error)
Groups(meta PageMetadata, token string) (GroupsPage, errors.SDKError)
// Parents returns page of users groups.
Parents(id string, offset, limit uint64, token string) (GroupsPage, error)
Parents(id string, offset, limit uint64, token string) (GroupsPage, errors.SDKError)
// Children returns page of users groups.
Children(id string, offset, limit uint64, token string) (GroupsPage, error)
Children(id string, offset, limit uint64, token string) (GroupsPage, errors.SDKError)
// Group returns users group object by id.
Group(id, token string) (Group, error)
Group(id, token string) (Group, errors.SDKError)
// Assign assigns member of member type (thing or user) to a group.
Assign(memberIDs []string, memberType, groupID string, token string) error
Assign(memberIDs []string, memberType, groupID string, token string) errors.SDKError
// Unassign removes member from a group.
Unassign(token, groupID string, memberIDs ...string) error
Unassign(token, groupID string, memberIDs ...string) errors.SDKError
// Members lists members of a group.
Members(groupID, token string, offset, limit uint64) (MembersPage, error)
Members(groupID, token string, offset, limit uint64) (MembersPage, errors.SDKError)
// Memberships lists groups for user.
Memberships(userID, token string, offset, limit uint64) (GroupsPage, error)
Memberships(userID, token string, offset, limit uint64) (GroupsPage, errors.SDKError)
// UpdateGroup updates existing group.
UpdateGroup(group Group, token string) error
UpdateGroup(group Group, token string) errors.SDKError
// Connect bulk connects things to channels specified by id.
Connect(conns ConnectionIDs, token string) error
Connect(conns ConnectionIDs, token string) errors.SDKError
// DisconnectThing disconnect thing from specified channel by id.
DisconnectThing(thingID, chanID, token string) error
DisconnectThing(thingID, chanID, token string) errors.SDKError
// CreateChannel creates new channel and returns its id.
CreateChannel(channel Channel, token string) (string, error)
CreateChannel(channel Channel, token string) (string, errors.SDKError)
// CreateChannels registers new channels and returns their ids.
CreateChannels(channels []Channel, token string) ([]Channel, error)
CreateChannels(channels []Channel, token string) ([]Channel, errors.SDKError)
// Channels returns page of channels.
Channels(token string, pm PageMetadata) (ChannelsPage, error)
Channels(token string, pm PageMetadata) (ChannelsPage, errors.SDKError)
// ChannelsByThing returns page of channels that are connected or not connected
// to specified thing.
ChannelsByThing(token, thingID string, offset, limit uint64, connected bool) (ChannelsPage, error)
ChannelsByThing(token, thingID string, offset, limit uint64, connected bool) (ChannelsPage, errors.SDKError)
// Channel returns channel data by id.
Channel(id, token string) (Channel, error)
Channel(id, token string) (Channel, errors.SDKError)
// UpdateChannel updates existing channel.
UpdateChannel(channel Channel, token string) error
UpdateChannel(channel Channel, token string) errors.SDKError
// DeleteChannel removes existing channel.
DeleteChannel(id, token string) error
DeleteChannel(id, token string) errors.SDKError
// SendMessage send message to specified channel.
SendMessage(chanID, msg, token string) error
SendMessage(chanID, msg, token string) errors.SDKError
// ReadMessages read messages of specified channel.
ReadMessages(chanID, token string) (MessagesPage, error)
ReadMessages(chanID, token string) (MessagesPage, errors.SDKError)
// SetContentType sets message content type.
SetContentType(ct ContentType) error
SetContentType(ct ContentType) errors.SDKError
// Health returns things service health check.
Health() (mainflux.HealthInfo, error)
Health() (mainflux.HealthInfo, errors.SDKError)
// AddBootstrap add bootstrap configuration
AddBootstrap(token string, cfg BootstrapConfig) (string, error)
AddBootstrap(token string, cfg BootstrapConfig) (string, errors.SDKError)
// View returns Thing Config with given ID belonging to the user identified by the given token.
ViewBootstrap(token, id string) (BootstrapConfig, error)
ViewBootstrap(token, id string) (BootstrapConfig, errors.SDKError)
// Update updates editable fields of the provided Config.
UpdateBootstrap(token string, cfg BootstrapConfig) error
UpdateBootstrap(token string, cfg BootstrapConfig) errors.SDKError
// Update boostrap config certificates
UpdateBootstrapCerts(token string, id string, clientCert, clientKey, ca string) error
UpdateBootstrapCerts(token string, id string, clientCert, clientKey, ca string) errors.SDKError
// Remove removes Config with specified token that belongs to the user identified by the given token.
RemoveBootstrap(token, id string) error
RemoveBootstrap(token, id string) errors.SDKError
// Bootstrap returns Config to the Thing with provided external ID using external key.
Bootstrap(externalKey, externalID string) (BootstrapConfig, error)
Bootstrap(externalKey, externalID string) (BootstrapConfig, errors.SDKError)
// Whitelist updates Thing state Config with given ID belonging to the user identified by the given token.
Whitelist(token string, cfg BootstrapConfig) error
Whitelist(token string, cfg BootstrapConfig) errors.SDKError
// IssueCert issues a certificate for a thing required for mtls.
IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, error)
IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, errors.SDKError)
// RemoveCert removes a certificate
RemoveCert(id, token string) error
RemoveCert(id, token string) errors.SDKError
// RevokeCert revokes certificate with certID for thing with thingID
RevokeCert(thingID, certID, token string) error
RevokeCert(thingID, certID, token string) errors.SDKError
// Issue issues a new key, returning its token value alongside.
Issue(token string, duration time.Duration) (KeyRes, error)
Issue(token string, duration time.Duration) (KeyRes, errors.SDKError)
// Revoke removes the key with the provided ID that is issued by the user identified by the provided key.
Revoke(token, id string) error
Revoke(token, id string) errors.SDKError
// RetrieveKey retrieves data for the key identified by the provided ID, that is issued by the user identified by the provided key.
RetrieveKey(token, id string) (retrieveKeyRes, error)
RetrieveKey(token, id string) (retrieveKeyRes, errors.SDKError)
}
type mfSDK struct {
@ -347,28 +302,41 @@ func NewSDK(conf Config) SDK {
}
}
func (sdk mfSDK) sendRequest(req *http.Request, token, contentType string) (*http.Response, error) {
// processRequest creates and send a new HTTP request, and checks for errors in the HTTP response.
// It then returns the response headers, the response body, and the associated error(s) (if any).
func (sdk mfSDK) processRequest(method, url, token, contentType string, data []byte, expectedRespCodes ...int) (http.Header, []byte, errors.SDKError) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
if token != "" {
req.Header.Set("Authorization", apiutil.BearerPrefix+token)
if !strings.Contains(token, apiutil.ThingPrefix) {
token = apiutil.BearerPrefix + token
}
req.Header.Set("Authorization", token)
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
}
return sdk.client.Do(req)
}
resp, err := sdk.client.Do(req)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
func (sdk mfSDK) sendThingRequest(req *http.Request, key, contentType string) (*http.Response, error) {
if key != "" {
req.Header.Set("Authorization", apiutil.ThingPrefix+key)
sdkerr := errors.CheckError(resp, expectedRespCodes...)
if sdkerr != nil {
return make(http.Header), []byte{}, sdkerr
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
return sdk.client.Do(req)
return resp.Header, body, nil
}
func (sdk mfSDK) withQueryParams(baseURL, endpoint string, pm PageMetadata) (string, error) {
@ -402,7 +370,7 @@ func (pm PageMetadata) query() (string, error) {
if pm.Metadata != nil {
md, err := json.Marshal(pm.Metadata)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
q.Add("metadata", string(md))
}

View File

@ -1,16 +0,0 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"fmt"
"net/http"
"github.com/mainflux/mainflux/pkg/errors"
)
func createError(e error, statusCode int) error {
httpStatus := fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode))
return errors.Wrap(e, errors.New(httpStatus))
}

View File

@ -4,10 +4,8 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -28,284 +26,151 @@ type identifyThingResp struct {
ID string `json:"id,omitempty"`
}
func (sdk mfSDK) CreateThing(t Thing, token string) (string, error) {
func (sdk mfSDK) CreateThing(t Thing, token string) (string, errors.SDKError) {
data, err := json.Marshal(t)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.thingsURL, thingsEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
id := strings.TrimPrefix(resp.Header.Get("Location"), fmt.Sprintf("/%s/", thingsEndpoint))
id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", thingsEndpoint))
return id, nil
}
func (sdk mfSDK) CreateThings(things []Thing, token string) ([]Thing, error) {
func (sdk mfSDK) CreateThings(things []Thing, token string) ([]Thing, errors.SDKError) {
data, err := json.Marshal(things)
if err != nil {
return []Thing{}, err
return []Thing{}, errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, thingsEndpoint, "bulk")
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return []Thing{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return []Thing{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return []Thing{}, errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return []Thing{}, err
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return []Thing{}, sdkerr
}
var ctr createThingsRes
if err := json.Unmarshal(body, &ctr); err != nil {
return []Thing{}, err
return []Thing{}, errors.NewSDKError(err)
}
return ctr.Things, nil
}
func (sdk mfSDK) Things(token string, pm PageMetadata) (ThingsPage, error) {
func (sdk mfSDK) Things(token string, pm PageMetadata) (ThingsPage, errors.SDKError) {
url, err := sdk.withQueryParams(sdk.thingsURL, thingsEndpoint, pm)
if err != nil {
return ThingsPage{}, err
return ThingsPage{}, errors.NewSDKError(err)
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return ThingsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return ThingsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ThingsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return ThingsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
_, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if sdkerr != nil {
return ThingsPage{}, sdkerr
}
var tp ThingsPage
if err := json.Unmarshal(body, &tp); err != nil {
return ThingsPage{}, err
return ThingsPage{}, errors.NewSDKError(err)
}
return tp, nil
}
func (sdk mfSDK) ThingsByChannel(token, chanID string, offset, limit uint64, disconn bool) (ThingsPage, error) {
func (sdk mfSDK) ThingsByChannel(token, chanID string, offset, limit uint64, disconn bool) (ThingsPage, errors.SDKError) {
url := fmt.Sprintf("%s/channels/%s/things?offset=%d&limit=%d&disconnected=%t", sdk.thingsURL, chanID, offset, limit, disconn)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return ThingsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return ThingsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ThingsPage{}, err
}
if resp.StatusCode != http.StatusOK {
return ThingsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var tp ThingsPage
if err := json.Unmarshal(body, &tp); err != nil {
return ThingsPage{}, err
return ThingsPage{}, errors.NewSDKError(err)
}
return tp, nil
}
func (sdk mfSDK) Thing(id, token string) (Thing, error) {
func (sdk mfSDK) Thing(id, token string) (Thing, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, thingsEndpoint, id)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return Thing{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return Thing{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Thing{}, err
}
if resp.StatusCode != http.StatusOK {
return Thing{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var t Thing
if err := json.Unmarshal(body, &t); err != nil {
return Thing{}, err
return Thing{}, errors.NewSDKError(err)
}
return t, nil
}
func (sdk mfSDK) UpdateThing(t Thing, token string) error {
func (sdk mfSDK) UpdateThing(t Thing, token string) errors.SDKError {
data, err := json.Marshal(t)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, thingsEndpoint, t.ID)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) DeleteThing(id, token string) error {
func (sdk mfSDK) DeleteThing(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s", sdk.thingsURL, thingsEndpoint, id)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}
func (sdk mfSDK) IdentifyThing(key string) (string, error) {
func (sdk mfSDK) IdentifyThing(key string) (string, errors.SDKError) {
idReq := identifyThingReq{Token: key}
data, err := json.Marshal(idReq)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.thingsURL, identifyEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
}
resp, err := sdk.sendRequest(req, "", string(CTJSON))
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
return "", errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, "", string(CTJSON), data, http.StatusOK)
if sdkerr != nil {
return "", sdkerr
}
var i identifyThingResp
if err := json.Unmarshal(body, &i); err != nil {
return "", err
return "", errors.NewSDKError(err)
}
return i.ID, err
return i.ID, nil
}
func (sdk mfSDK) Connect(connIDs ConnectionIDs, token string) error {
func (sdk mfSDK) Connect(connIDs ConnectionIDs, token string) errors.SDKError {
data, err := json.Marshal(connIDs)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.thingsURL, connectEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedConnect, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) DisconnectThing(thingID, chanID, token string) error {
func (sdk mfSDK) DisconnectThing(thingID, chanID, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/%s/%s", sdk.thingsURL, channelsEndpoint, chanID, thingsEndpoint, thingID)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedDisconnect, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}

View File

@ -9,7 +9,9 @@ import (
"net/http/httptest"
"testing"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
sdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/mainflux/mainflux/things"
@ -29,9 +31,7 @@ const (
token = "token"
otherToken = "other_token"
wrongValue = "wrong_value"
badID = "999"
badKey = "999"
emptyValue = ""
)
var (
@ -107,14 +107,14 @@ func TestCreateThing(t *testing.T) {
desc: "create new thing with empty token",
thing: th1,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
location: "",
},
{
desc: "create new thing with invalid token",
thing: th1,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
location: "",
},
}
@ -170,21 +170,21 @@ func TestCreateThings(t *testing.T) {
desc: "create new things with empty things",
things: []sdk.Thing{},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrEmptyList, http.StatusBadRequest),
res: []sdk.Thing{},
},
{
desc: "create new thing with empty token",
things: things,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
res: []sdk.Thing{},
},
{
desc: "create new thing with invalid token",
things: things,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
res: []sdk.Thing{},
},
{
@ -198,7 +198,7 @@ func TestCreateThings(t *testing.T) {
desc: "create new things with wrong external UUID",
things: thsWrongExtID,
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrInvalidIDFormat, http.StatusBadRequest),
res: []sdk.Thing{},
},
}
@ -246,14 +246,14 @@ func TestThing(t *testing.T) {
desc: "get non-existent thing",
thID: "43",
token: token,
err: createError(sdk.ErrFailedFetch, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
response: sdk.Thing{},
},
{
desc: "get thing with invalid token",
thID: id,
token: wrongValue,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: sdk.Thing{},
},
}
@ -311,7 +311,7 @@ func TestThings(t *testing.T) {
token: wrongValue,
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: nil,
metadata: make(map[string]interface{}),
},
@ -320,7 +320,7 @@ func TestThings(t *testing.T) {
token: "",
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
response: nil,
metadata: make(map[string]interface{}),
},
@ -329,7 +329,7 @@ func TestThings(t *testing.T) {
token: token,
offset: 0,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
metadata: make(map[string]interface{}),
},
@ -338,7 +338,7 @@ func TestThings(t *testing.T) {
token: token,
offset: offset,
limit: 110,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
metadata: make(map[string]interface{}),
},
@ -438,7 +438,7 @@ func TestThingsByChannel(t *testing.T) {
token: wrongValue,
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: nil,
},
{
@ -447,7 +447,7 @@ func TestThingsByChannel(t *testing.T) {
token: "",
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
response: nil,
},
{
@ -456,7 +456,7 @@ func TestThingsByChannel(t *testing.T) {
token: token,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -465,7 +465,7 @@ func TestThingsByChannel(t *testing.T) {
token: token,
offset: offset,
limit: 110,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -483,7 +483,7 @@ func TestThingsByChannel(t *testing.T) {
token: wrongValue,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
response: nil,
},
{
@ -543,17 +543,17 @@ func TestUpdateThing(t *testing.T) {
Metadata: metadata,
},
token: token,
err: createError(sdk.ErrFailedUpdate, http.StatusForbidden),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthorization, http.StatusForbidden),
},
{
desc: "update channel with invalid id",
desc: "update channel with an empty id",
thing: sdk.Thing{
ID: "",
Name: "test_device",
Metadata: metadata,
},
token: token,
err: createError(sdk.ErrFailedUpdate, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "update channel with invalid token",
@ -563,7 +563,7 @@ func TestUpdateThing(t *testing.T) {
Metadata: metadata2,
},
token: wrongValue,
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "update channel with empty token",
@ -573,7 +573,7 @@ func TestUpdateThing(t *testing.T) {
Metadata: metadata2,
},
token: "",
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
}
@ -607,25 +607,25 @@ func TestDeleteThing(t *testing.T) {
desc: "delete thing with invalid token",
thingID: id,
token: wrongValue,
err: createError(sdk.ErrFailedRemoval, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "delete non-existing thing",
thingID: "2",
token: token,
err: createError(sdk.ErrFailedRemoval, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "delete thing with invalid id",
thingID: "",
token: token,
err: createError(sdk.ErrFailedRemoval, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "delete thing with empty token",
thingID: id,
token: "",
err: createError(sdk.ErrFailedRemoval, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
{
desc: "delete existing thing",
@ -679,21 +679,21 @@ func TestIdentifyThing(t *testing.T) {
response string
}{
{
desc: "identify thing with valid key",
desc: "identify thing with a valid key",
thingKey: thing.Key,
err: nil,
response: id,
},
{
desc: "identify thing with invalid key",
desc: "identify thing with an invalid key",
thingKey: badKey,
err: createError(sdk.ErrFailedFetch, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
response: "",
},
{
desc: "identify thing with empty key",
desc: "identify thing with an empty key",
thingKey: "",
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerKey, http.StatusUnauthorized),
response: "",
},
}
@ -749,28 +749,28 @@ func TestConnectThing(t *testing.T) {
thingID: thingID,
chanID: "9",
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "connect non-existing thing to existing channel",
thingID: "9",
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "connect existing thing to channel with invalid ID",
thingID: thingID,
chanID: "",
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "connect thing with invalid ID to existing channel",
desc: "connect thing with missing ID to existing channel",
thingID: "",
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
@ -778,21 +778,21 @@ func TestConnectThing(t *testing.T) {
thingID: thingID,
chanID: chanID1,
token: wrongValue,
err: createError(sdk.ErrFailedConnect, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "connect existing thing to existing channel with empty token",
thingID: thingID,
chanID: chanID1,
token: "",
err: createError(sdk.ErrFailedConnect, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
{
desc: "connect thing from owner to channel of other user",
thingID: thingID,
chanID: chanID2,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
}
@ -806,108 +806,6 @@ func TestConnectThing(t *testing.T) {
}
}
func TestConnect(t *testing.T) {
svc := newThingsService(map[string]string{
token: email,
otherToken: otherEmail,
})
ts := newThingsServer(svc)
defer ts.Close()
sdkConf := sdk.Config{
ThingsURL: ts.URL,
MsgContentType: contentType,
TLSVerification: false,
}
mainfluxSDK := sdk.NewSDK(sdkConf)
thingID, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
chanID1, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
chanID2, err := mainfluxSDK.CreateChannel(ch3, otherToken)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
desc string
thingID string
chanID string
token string
err error
}{
{
desc: "connect existing things to existing channels",
thingID: thingID,
chanID: chanID1,
token: token,
err: nil,
},
{
desc: "connect existing things to non-existing channels",
thingID: thingID,
chanID: badID,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
},
{
desc: "connect non-existing things to existing channels",
thingID: badID,
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
},
{
desc: "connect existing things to channels with invalid ID",
thingID: thingID,
chanID: emptyValue,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusBadRequest),
},
{
desc: "connect things with invalid ID to existing channels",
thingID: emptyValue,
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusBadRequest),
},
{
desc: "connect existing things to existing channels with invalid token",
thingID: thingID,
chanID: chanID1,
token: wrongValue,
err: createError(sdk.ErrFailedConnect, http.StatusUnauthorized),
},
{
desc: "connect existing things to existing channels with empty token",
thingID: thingID,
chanID: chanID1,
token: emptyValue,
err: createError(sdk.ErrFailedConnect, http.StatusUnauthorized),
},
{
desc: "connect things from owner to channels of other user",
thingID: thingID,
chanID: chanID2,
token: token,
err: createError(sdk.ErrFailedConnect, http.StatusNotFound),
},
}
for _, tc := range cases {
connIDs := sdk.ConnectionIDs{
[]string{tc.thingID},
[]string{tc.chanID},
}
err := mainfluxSDK.Connect(connIDs, tc.token)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected error %s, got %s", tc.desc, tc.err, err))
}
}
func TestDisconnectThing(t *testing.T) {
svc := newThingsService(map[string]string{
token: email,
@ -959,49 +857,49 @@ func TestDisconnectThing(t *testing.T) {
thingID: thingID,
chanID: "9",
token: token,
err: createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "disconnect non-existing thing from existing channel",
thingID: "9",
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
{
desc: "disconnect existing thing from channel with invalid ID",
thingID: thingID,
chanID: "",
token: token,
err: createError(sdk.ErrFailedDisconnect, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "disconnect thing with invalid ID from existing channel",
thingID: "",
chanID: chanID1,
token: token,
err: createError(sdk.ErrFailedDisconnect, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrMissingID, http.StatusBadRequest),
},
{
desc: "disconnect existing thing from existing channel with invalid token",
thingID: thingID,
chanID: chanID1,
token: wrongValue,
err: createError(sdk.ErrFailedDisconnect, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "disconnect existing thing from existing channel with empty token",
thingID: thingID,
chanID: chanID1,
token: "",
err: createError(sdk.ErrFailedDisconnect, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
{
desc: "disconnect owner's thing from someone elses channel",
thingID: thingID,
chanID: chanID2,
token: token,
err: createError(sdk.ErrFailedDisconnect, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
},
}

View File

@ -4,10 +4,8 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
@ -21,211 +19,116 @@ const (
membersEndpoint = "members"
)
func (sdk mfSDK) CreateUser(token string, u User) (string, error) {
func (sdk mfSDK) CreateUser(token string, u User) (string, errors.SDKError) {
data, err := json.Marshal(u)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.usersURL, usersEndpoint)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return "", err
headers, _, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusCreated {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
}
id := strings.TrimPrefix(resp.Header.Get("Location"), fmt.Sprintf("/%s/", usersEndpoint))
id := strings.TrimPrefix(headers.Get("Location"), fmt.Sprintf("/%s/", usersEndpoint))
return id, nil
}
func (sdk mfSDK) User(userID, token string) (User, error) {
func (sdk mfSDK) User(userID, token string) (User, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.usersURL, usersEndpoint, userID)
req, err := http.NewRequest(http.MethodGet, url, nil)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return User{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return User{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return User{}, err
}
if resp.StatusCode != http.StatusOK {
return User{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var u User
if err := json.Unmarshal(body, &u); err != nil {
return User{}, err
return User{}, errors.NewSDKError(err)
}
return u, nil
}
func (sdk mfSDK) Users(token string, pm PageMetadata) (UsersPage, error) {
url, err := sdk.withQueryParams(sdk.usersURL, usersEndpoint, pm)
if err != nil {
return UsersPage{}, err
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return UsersPage{}, err
func (sdk mfSDK) Users(token string, pm PageMetadata) (UsersPage, errors.SDKError) {
var url string
var err error
if url, err = sdk.withQueryParams(sdk.usersURL, usersEndpoint, pm); err != nil {
return UsersPage{}, errors.NewSDKError(err)
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return UsersPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return UsersPage{}, err
_, body, sdkerr := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if sdkerr != nil {
return UsersPage{}, sdkerr
}
if resp.StatusCode != http.StatusOK {
return UsersPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
var up UsersPage
if err := json.Unmarshal(body, &up); err != nil {
return UsersPage{}, err
return UsersPage{}, errors.NewSDKError(err)
}
return up, nil
}
func (sdk mfSDK) CreateToken(user User) (string, error) {
func (sdk mfSDK) CreateToken(user User) (string, errors.SDKError) {
data, err := json.Marshal(user)
if err != nil {
return "", err
return "", errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.usersURL, tokensEndpoint)
resp, err := sdk.client.Post(url, string(CTJSON), bytes.NewReader(data))
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusCreated {
return "", errors.Wrap(ErrFailedCreation, errors.New(resp.Status))
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, "", string(CTJSON), data, http.StatusCreated)
if sdkerr != nil {
return "", sdkerr
}
var tr tokenRes
if err := json.Unmarshal(body, &tr); err != nil {
return "", err
return "", errors.NewSDKError(err)
}
return tr.Token, nil
}
func (sdk mfSDK) UpdateUser(u User, token string) error {
func (sdk mfSDK) UpdateUser(u User, token string) errors.SDKError {
data, err := json.Marshal(u)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.usersURL, usersEndpoint)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPut, url, token, string(CTJSON), data, http.StatusOK)
return sdkerr
}
func (sdk mfSDK) UpdatePassword(oldPass, newPass, token string) error {
func (sdk mfSDK) UpdatePassword(oldPass, newPass, token string) errors.SDKError {
ur := UserPasswordReq{
OldPassword: oldPass,
Password: newPass,
}
data, err := json.Marshal(ur)
if err != nil {
return err
return errors.NewSDKError(err)
}
url := fmt.Sprintf("%s/%s", sdk.usersURL, passwordEndpoint)
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusCreated {
return errors.Wrap(ErrFailedUpdate, errors.New(resp.Status))
}
return nil
_, _, sdkerr := sdk.processRequest(http.MethodPatch, url, token, string(CTJSON), data, http.StatusCreated)
return sdkerr
}
func (sdk mfSDK) EnableUser(id, token string) error {
func (sdk mfSDK) EnableUser(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/enable", sdk.usersURL, usersEndpoint, id)
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}
func (sdk mfSDK) DisableUser(id, token string) error {
func (sdk mfSDK) DisableUser(id, token string) errors.SDKError {
url := fmt.Sprintf("%s/%s/%s/disable", sdk.usersURL, usersEndpoint, id)
req, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
return err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
return err
}
if resp.StatusCode != http.StatusNoContent {
return errors.Wrap(ErrFailedRemoval, errors.New(resp.Status))
}
return nil
_, _, err := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), nil, http.StatusNoContent)
return err
}

View File

@ -13,7 +13,9 @@ import (
"github.com/mainflux/mainflux"
mfauth "github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/internal/apiutil"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
sdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/mainflux/mainflux/users"
@ -80,7 +82,7 @@ func TestCreateUser(t *testing.T) {
desc string
user sdk.User
token string
err error
err errors.SDKError
}{
{
desc: "register new user",
@ -92,37 +94,37 @@ func TestCreateUser(t *testing.T) {
desc: "register existing user",
user: user,
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusConflict),
err: errors.NewSDKErrorWithStatus(errors.ErrConflict, http.StatusConflict),
},
{
desc: "register user with invalid email address",
user: sdk.User{Email: invalidEmail, Password: "password"},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest),
},
{
desc: "register user with empty password",
user: sdk.User{Email: "user2@example.com", Password: ""},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(users.ErrPasswordFormat, http.StatusBadRequest),
},
{
desc: "register user without password",
user: sdk.User{Email: "user2@example.com"},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(users.ErrPasswordFormat, http.StatusBadRequest),
},
{
desc: "register user without email",
user: sdk.User{Password: "password"},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest),
},
{
desc: "register empty user",
user: sdk.User{},
token: token,
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest),
},
}
@ -162,7 +164,7 @@ func TestUser(t *testing.T) {
desc string
userID string
token string
err error
err errors.SDKError
response sdk.User
}{
{
@ -176,7 +178,7 @@ func TestUser(t *testing.T) {
desc: "get non-existent user",
userID: "43",
token: usertoken,
err: createError(sdk.ErrFailedFetch, http.StatusNotFound),
err: errors.NewSDKErrorWithStatus(errors.ErrNotFound, http.StatusNotFound),
response: sdk.User{},
},
@ -184,7 +186,7 @@ func TestUser(t *testing.T) {
desc: "get user with invalid token",
userID: userID,
token: wrongValue,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
response: sdk.User{},
},
}
@ -234,7 +236,7 @@ func TestUsers(t *testing.T) {
token string
offset uint64
limit uint64
err error
err errors.SDKError
response []sdk.User
email string
metadata map[string]interface{}
@ -253,7 +255,7 @@ func TestUsers(t *testing.T) {
token: wrongValue,
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
email: "",
response: nil,
},
@ -262,7 +264,7 @@ func TestUsers(t *testing.T) {
token: "",
offset: offset,
limit: limit,
err: createError(sdk.ErrFailedFetch, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
email: "",
response: nil,
},
@ -271,7 +273,7 @@ func TestUsers(t *testing.T) {
token: token,
offset: offset,
limit: 0,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
email: "",
response: nil,
},
@ -280,7 +282,7 @@ func TestUsers(t *testing.T) {
token: token,
offset: offset,
limit: 110,
err: createError(sdk.ErrFailedFetch, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(apiutil.ErrLimitSize, http.StatusBadRequest),
email: "",
response: []sdk.User(nil),
},
@ -347,7 +349,7 @@ func TestCreateToken(t *testing.T) {
desc string
user sdk.User
token string
err error
err errors.SDKError
}{
{
desc: "create token for user",
@ -359,13 +361,13 @@ func TestCreateToken(t *testing.T) {
desc: "create token for non existing user",
user: sdk.User{Email: "user2@example.com", Password: "password"},
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "create user with empty email",
user: sdk.User{Email: "", Password: "password"},
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
err: errors.NewSDKErrorWithStatus(errors.ErrMalformedEntity, http.StatusBadRequest),
},
}
for _, tc := range cases {
@ -403,7 +405,7 @@ func TestUpdateUser(t *testing.T) {
desc string
user sdk.User
token string
err error
err errors.SDKError
}{
{
desc: "update email for user",
@ -415,13 +417,13 @@ func TestUpdateUser(t *testing.T) {
desc: "update email for user with invalid token",
user: sdk.User{ID: userID, Email: "user2@example.com", Password: "password"},
token: wrongValue,
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "update email for user with empty token",
user: sdk.User{ID: userID, Email: "user2@example.com", Password: "password"},
token: "",
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
{
desc: "update metadata for user",
@ -465,7 +467,7 @@ func TestUpdatePassword(t *testing.T) {
oldPass string
newPass string
token string
err error
err errors.SDKError
}{
{
desc: "update password for user",
@ -479,14 +481,14 @@ func TestUpdatePassword(t *testing.T) {
oldPass: "password",
newPass: "password123",
token: wrongValue,
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(errors.ErrAuthentication, http.StatusUnauthorized),
},
{
desc: "update password for user with empty token",
oldPass: "password",
newPass: "password123",
token: "",
err: createError(sdk.ErrFailedUpdate, http.StatusUnauthorized),
err: errors.NewSDKErrorWithStatus(apiutil.ErrBearerToken, http.StatusUnauthorized),
},
}
for _, tc := range cases {

View File

@ -23,6 +23,7 @@ var (
ErrUnauthorized = errors.New("unauthorized access")
ErrFailedToCreateToken = errors.New("failed to create access token")
ErrEmptyThingsList = errors.New("things list in configuration empty")
ErrThingUpdate = errors.New("failed to update thing")
ErrEmptyChannelsList = errors.New("channels list in configuration is empty")
ErrFailedChannelCreation = errors.New("failed to create channel")
ErrFailedChannelRetrieval = errors.New("failed to retrieve channel")
@ -239,7 +240,7 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin
}
if err := ps.sdk.Whitelist(token, wlReq); err != nil {
res.Error = err.Error()
return res, SDK.ErrFailedWhitelist
return res, ErrThingUpdate
}
res.Whitelisted[thing.ID] = true
}
@ -311,9 +312,9 @@ func (ps *provisionService) updateGateway(token string, bs SDK.BootstrapConfig,
gw.CfgID = bs.MFThing
gw.Type = gateway
th, err := ps.sdk.Thing(bs.MFThing, token)
if err != nil {
return errors.Wrap(ErrGatewayUpdate, err)
th, sdkerr := ps.sdk.Thing(bs.MFThing, token)
if sdkerr != nil {
return errors.Wrap(ErrGatewayUpdate, sdkerr)
}
b, err := json.Marshal(gw)
if err != nil {
@ -344,10 +345,11 @@ func clean(ps *provisionService, things []SDK.Thing, channels []SDK.Channel, tok
}
func (ps *provisionService) recover(e *error, ths *[]SDK.Thing, chs *[]SDK.Channel, tkn *string) {
things, channels, token, err := *ths, *chs, *tkn, *e
if e == nil {
return
}
things, channels, token, err := *ths, *chs, *tkn, *e
if errors.Contains(err, ErrFailedThingRetrieval) || errors.Contains(err, ErrFailedChannelCreation) {
for _, th := range things {
ps.errLog(ps.sdk.DeleteThing(th.ID, token))
@ -382,7 +384,7 @@ func (ps *provisionService) recover(e *error, ths *[]SDK.Thing, chs *[]SDK.Chann
}
}
if errors.Contains(err, SDK.ErrFailedWhitelist) || errors.Contains(err, ErrGatewayUpdate) {
if errors.Contains(err, ErrThingUpdate) || errors.Contains(err, ErrGatewayUpdate) {
clean(ps, things, channels, token)
for _, th := range things {
if ps.conf.Bootstrap.X509Provision && needsBootstrap(th) {

View File

@ -11,7 +11,7 @@ type identifyReq struct {
func (req identifyReq) validate() error {
if req.Token == "" {
return apiutil.ErrBearerToken
return apiutil.ErrBearerKey
}
return nil
@ -24,7 +24,7 @@ type canAccessByKeyReq struct {
func (req canAccessByKeyReq) validate() error {
if req.Token == "" {
return apiutil.ErrBearerToken
return apiutil.ErrBearerKey
}
if req.chanID == "" {

View File

@ -115,7 +115,8 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch {
case err == apiutil.ErrBearerToken,
case errors.Contains(err, apiutil.ErrBearerToken),
errors.Contains(err, apiutil.ErrBearerKey),
errors.Contains(err, errors.ErrAuthentication):
w.WriteHeader(http.StatusUnauthorized)
case errors.Contains(err, errors.ErrNotFound):
@ -125,7 +126,7 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
case errors.Contains(err, errors.ErrUnsupportedContentType):
w.WriteHeader(http.StatusUnsupportedMediaType)
case errors.Contains(err, errors.ErrMalformedEntity),
err == apiutil.ErrMissingID:
errors.Contains(err, apiutil.ErrMissingID):
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)

View File

@ -89,6 +89,8 @@ func Provision(conf Config) {
}
var err error
// Login user
token, err := s.CreateToken(user)
if err != nil {
@ -119,7 +121,6 @@ func Provision(conf Config) {
if err != nil {
log.Fatalf("Failed to decode certificate - %s", err.Error())
}
}
// Create things and channels
@ -159,7 +160,7 @@ func Provision(conf Config) {
if conf.SSL {
var priv interface{}
priv, err = rsa.GenerateKey(rand.Reader, rsaBits)
priv, _ = rsa.GenerateKey(rand.Reader, rsaBits)
notBefore := time.Now()
validFor, err := time.ParseDuration(ttl)