Skip to content

Commit

Permalink
feat: Use URL escape for device command name and resource name
Browse files Browse the repository at this point in the history
Remove the constraint of device command name and resource name, and use URL escape in client API.

Close #773

Signed-off-by: bruce <weichou1229@gmail.com>
  • Loading branch information
weichou1229 committed Feb 21, 2023
1 parent 1bb5704 commit abb2fde
Show file tree
Hide file tree
Showing 12 changed files with 45 additions and 41 deletions.
10 changes: 5 additions & 5 deletions clients/http/command.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021-2022 IOTech Ltd
// Copyright (C) 2021-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -59,7 +59,7 @@ func (client *CommandClient) IssueGetCommandByName(ctx context.Context, deviceNa
requestParams := url.Values{}
requestParams.Set(common.PushEvent, strconv.FormatBool(dsPushEvent))
requestParams.Set(common.ReturnEvent, strconv.FormatBool(dsReturnEvent))
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -73,7 +73,7 @@ func (client *CommandClient) IssueGetCommandByNameWithQueryParams(ctx context.Co
requestParams.Set(k, v)
}

requestPath := path.Join(common.ApiDeviceRoute, common.Name, url.QueryEscape(deviceName), url.QueryEscape(commandName))
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -83,7 +83,7 @@ func (client *CommandClient) IssueGetCommandByNameWithQueryParams(ctx context.Co

// IssueSetCommandByName issues the specified write command referenced by the command name to the device/sensor that is also referenced by name.
func (client *CommandClient) IssueSetCommandByName(ctx context.Context, deviceName string, commandName string, settings map[string]string) (res dtoCommon.BaseResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
err = utils.PutRequest(ctx, &res, client.baseUrl, requestPath, nil, settings)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -93,7 +93,7 @@ func (client *CommandClient) IssueSetCommandByName(ctx context.Context, deviceNa

// IssueSetCommandByNameWithObject issues the specified write command and the settings supports object value type
func (client *CommandClient) IssueSetCommandByNameWithObject(ctx context.Context, deviceName string, commandName string, settings map[string]interface{}) (res dtoCommon.BaseResponse, err errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
err = utils.PutRequest(ctx, &res, client.baseUrl, requestPath, nil, settings)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down
8 changes: 4 additions & 4 deletions clients/http/deviceprofile.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2020-2022 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -159,7 +159,7 @@ func (client *DeviceProfileClient) DeviceResourceByProfileNameAndResourceName(ct
if exists {
return res, nil
}
requestPath := path.Join(common.ApiDeviceResourceRoute, common.Profile, profileName, common.Resource, resourceName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceResourceRoute, common.Profile, profileName, common.Resource, resourceName)
err := utils.GetRequest(ctx, &res, client.baseUrl, requestPath, nil)
if err != nil {
return res, errors.NewCommonEdgeXWrapper(err)
Expand Down Expand Up @@ -220,7 +220,7 @@ func (client *DeviceProfileClient) UpdateDeviceProfileResource(ctx context.Conte
// DeleteDeviceResourceByName deletes device resource by name
func (client *DeviceProfileClient) DeleteDeviceResourceByName(ctx context.Context, profileName string, resourceName string) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiDeviceProfileRoute, common.Name, url.QueryEscape(profileName), common.Resource, url.QueryEscape(resourceName))
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceProfileRoute, common.Name, profileName, common.Resource, resourceName)
err := utils.DeleteRequest(ctx, &response, client.baseUrl, requestPath)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand Down Expand Up @@ -251,7 +251,7 @@ func (client *DeviceProfileClient) UpdateDeviceProfileDeviceCommand(ctx context.
// DeleteDeviceCommandByName deletes device command by name
func (client *DeviceProfileClient) DeleteDeviceCommandByName(ctx context.Context, profileName string, commandName string) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiDeviceProfileRoute, common.Name, url.QueryEscape(profileName), common.DeviceCommand, url.QueryEscape(commandName))
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceProfileRoute, common.Name, profileName, common.DeviceCommand, commandName)
err := utils.DeleteRequest(ctx, &response, client.baseUrl, requestPath)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand Down
9 changes: 4 additions & 5 deletions clients/http/deviceservicecommand.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021 IOTech Ltd
// Copyright (C) 2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand All @@ -9,7 +9,6 @@ import (
"context"
"encoding/json"
"net/url"
"path"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/http/utils"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces"
Expand All @@ -30,7 +29,7 @@ func NewDeviceServiceCommandClient() interfaces.DeviceServiceCommandClient {

// GetCommand sends HTTP request to execute the Get command
func (client *deviceServiceCommandClient) GetCommand(ctx context.Context, baseUrl string, deviceName string, commandName string, queryParams string) (*responses.EventResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
params, err := url.ParseQuery(queryParams)
if err != nil {
return nil, errors.NewCommonEdgeXWrapper(err)
Expand Down Expand Up @@ -60,7 +59,7 @@ func (client *deviceServiceCommandClient) GetCommand(ctx context.Context, baseUr
// SetCommand sends HTTP request to execute the Set command
func (client *deviceServiceCommandClient) SetCommand(ctx context.Context, baseUrl string, deviceName string, commandName string, queryParams string, settings map[string]string) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
params, err := url.ParseQuery(queryParams)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand All @@ -75,7 +74,7 @@ func (client *deviceServiceCommandClient) SetCommand(ctx context.Context, baseUr
// SetCommandWithObject invokes device service's set command API and the settings supports object value type
func (client *deviceServiceCommandClient) SetCommandWithObject(ctx context.Context, baseUrl string, deviceName string, commandName string, queryParams string, settings map[string]interface{}) (dtoCommon.BaseResponse, errors.EdgeX) {
var response dtoCommon.BaseResponse
requestPath := path.Join(common.ApiDeviceRoute, common.Name, deviceName, commandName)
requestPath := utils.EscapeAndJoinPath(common.ApiDeviceRoute, common.Name, deviceName, commandName)
params, err := url.ParseQuery(queryParams)
if err != nil {
return response, errors.NewCommonEdgeXWrapper(err)
Expand Down
12 changes: 6 additions & 6 deletions clients/http/reading.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2020-2021 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -75,7 +75,7 @@ func (rc readingClient) ReadingsByDeviceName(ctx context.Context, name string, o
}

func (rc readingClient) ReadingsByResourceName(ctx context.Context, name string, offset, limit int) (responses.MultiReadingsResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiReadingRoute, common.ResourceName, name)
requestPath := utils.EscapeAndJoinPath(common.ApiReadingRoute, common.ResourceName, name)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -102,7 +102,7 @@ func (rc readingClient) ReadingsByTimeRange(ctx context.Context, start, end, off

// ReadingsByResourceNameAndTimeRange returns readings by resource name and specified time range. Readings are sorted in descending order of origin time.
func (rc readingClient) ReadingsByResourceNameAndTimeRange(ctx context.Context, name string, start, end, offset, limit int) (responses.MultiReadingsResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiReadingRoute, common.ResourceName, name, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestPath := utils.EscapeAndJoinPath(common.ApiReadingRoute, common.ResourceName, name, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -115,7 +115,7 @@ func (rc readingClient) ReadingsByResourceNameAndTimeRange(ctx context.Context,
}

func (rc readingClient) ReadingsByDeviceNameAndResourceName(ctx context.Context, deviceName, resourceName string, offset, limit int) (responses.MultiReadingsResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.ResourceName, resourceName)
requestPath := utils.EscapeAndJoinPath(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.ResourceName, resourceName)
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -129,7 +129,7 @@ func (rc readingClient) ReadingsByDeviceNameAndResourceName(ctx context.Context,
}

func (rc readingClient) ReadingsByDeviceNameAndResourceNameAndTimeRange(ctx context.Context, deviceName, resourceName string, start, end, offset, limit int) (responses.MultiReadingsResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.ResourceName, resourceName, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestPath := utils.EscapeAndJoinPath(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.ResourceName, resourceName, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand All @@ -142,7 +142,7 @@ func (rc readingClient) ReadingsByDeviceNameAndResourceNameAndTimeRange(ctx cont
}

func (rc readingClient) ReadingsByDeviceNameAndResourceNamesAndTimeRange(ctx context.Context, deviceName string, resourceNames []string, start, end, offset, limit int) (responses.MultiReadingsResponse, errors.EdgeX) {
requestPath := path.Join(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestPath := utils.EscapeAndJoinPath(common.ApiReadingRoute, common.Device, common.Name, deviceName, common.Start, strconv.Itoa(start), common.End, strconv.Itoa(end))
requestParams := url.Values{}
requestParams.Set(common.Offset, strconv.Itoa(offset))
requestParams.Set(common.Limit, strconv.Itoa(limit))
Expand Down
2 changes: 1 addition & 1 deletion clients/http/reading_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func TestQueryReadingsByDeviceNameAndResourceNamesAndTimeRange(t *testing.T) {
defer ts.Close()

client := NewReadingClient(ts.URL)
res, err := client.ReadingsByDeviceNameAndResourceNamesAndTimeRange(context.Background(), deviceName, resourceNames, start, end, 1, 10)
res, err := client.ReadingsByDeviceNameAndResourceNamesAndTimeRange(context.Background(), deviceName, resourceNames, start, end, start, end)
require.NoError(t, err)
assert.IsType(t, responses.MultiReadingsResponse{}, res)
}
27 changes: 16 additions & 11 deletions clients/http/utils/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2020-2022 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -67,11 +67,10 @@ func makeRequest(req *http.Request) (*http.Response, errors.EdgeX) {
}

func createRequest(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values) (*http.Request, errors.EdgeX) {
u, err := url.Parse(baseUrl)
u, err := url.Parse(baseUrl + requestPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to parse baseUrl", err)
}
u.Path = path.Join(u.Path, requestPath)
if requestParams != nil {
u.RawQuery = requestParams.Encode()
}
Expand All @@ -84,11 +83,10 @@ func createRequest(ctx context.Context, httpMethod string, baseUrl string, reque
}

func createRequestWithRawDataAndParams(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values, data interface{}) (*http.Request, errors.EdgeX) {
u, err := url.Parse(baseUrl)
u, err := url.Parse(baseUrl + requestPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to parse baseUrl", err)
}
u.Path = path.Join(u.Path, requestPath)
if requestParams != nil {
u.RawQuery = requestParams.Encode()
}
Expand All @@ -112,11 +110,10 @@ func createRequestWithRawDataAndParams(ctx context.Context, httpMethod string, b
}

func createRequestWithRawData(ctx context.Context, httpMethod string, baseUrl string, requestPath string, requestParams url.Values, data interface{}) (*http.Request, errors.EdgeX) {
u, err := url.Parse(baseUrl)
u, err := url.Parse(baseUrl + requestPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to parse baseUrl", err)
}
u.Path = path.Join(u.Path, requestPath)
if requestParams != nil {
u.RawQuery = requestParams.Encode()
}
Expand All @@ -141,11 +138,10 @@ func createRequestWithRawData(ctx context.Context, httpMethod string, baseUrl st
}

func createRequestWithEncodedData(ctx context.Context, httpMethod string, baseUrl string, requestPath string, data []byte, encoding string) (*http.Request, errors.EdgeX) {
u, err := url.Parse(baseUrl)
u, err := url.Parse(baseUrl + requestPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to parse baseUrl", err)
}
u.Path = path.Join(u.Path, requestPath)

content := encoding
if content == "" {
Expand All @@ -163,11 +159,10 @@ func createRequestWithEncodedData(ctx context.Context, httpMethod string, baseUr

// createRequestFromFilePath creates multipart/form-data request with the specified file
func createRequestFromFilePath(ctx context.Context, httpMethod string, baseUrl string, requestPath string, filePath string) (*http.Request, errors.EdgeX) {
u, err := url.Parse(baseUrl)
u, err := url.Parse(baseUrl + requestPath)
if err != nil {
return nil, errors.NewCommonEdgeX(errors.KindServerError, "fail to parse baseUrl", err)
}
u.Path = path.Join(u.Path, requestPath)

fileContents, err := ioutil.ReadFile(filePath)
if err != nil {
Expand Down Expand Up @@ -218,3 +213,13 @@ func sendRequest(ctx context.Context, req *http.Request) ([]byte, errors.EdgeX)
errKind := errors.KindMapping(resp.StatusCode)
return nil, errors.NewCommonEdgeX(errKind, msg, nil)
}

// EscapeAndJoinPath escape and join the path variables
func EscapeAndJoinPath(apiRoutePath string, pathVariables ...string) string {
elements := make([]string, len(pathVariables)+1)
elements[0] = apiRoutePath // we don't need to escape the route path like /device, /reading, ...,etc.
for i, e := range pathVariables {
elements[i+1] = url.QueryEscape(e)
}
return path.Join(elements...)
}
2 changes: 1 addition & 1 deletion dtos/corecommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type DeviceCoreCommand struct {
// CoreCommand and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-command/2.1.0#/CoreCommand
type CoreCommand struct {
Name string `json:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name string `json:"name" validate:"required,edgex-dto-none-empty-strin"`
Get bool `json:"get,omitempty" validate:"required_without=Set"`
Set bool `json:"set,omitempty" validate:"required_without=Get"`
Path string `json:"path,omitempty"`
Expand Down
4 changes: 2 additions & 2 deletions dtos/devicecommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v3/models"
// DeviceCommand and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.1.0#/DeviceCommand
type DeviceCommand struct {
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"`
IsHidden bool `json:"isHidden" yaml:"isHidden"`
ReadWrite string `json:"readWrite" yaml:"readWrite" validate:"required,oneof='R' 'W' 'RW' 'WR'"`
ResourceOperations []ResourceOperation `json:"resourceOperations" yaml:"resourceOperations" validate:"gt=0,dive"`
Expand All @@ -20,7 +20,7 @@ type DeviceCommand struct {
// UpdateDeviceCommand and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.2.0#/DeviceCommand
type UpdateDeviceCommand struct {
Name *string `json:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name *string `json:"name" validate:"required,edgex-dto-none-empty-string"`
IsHidden *bool `json:"isHidden"`
}

Expand Down
4 changes: 2 additions & 2 deletions dtos/deviceresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v3/models"
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.1.0#/DeviceResource
type DeviceResource struct {
Description string `json:"description" yaml:"description"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"`
IsHidden bool `json:"isHidden" yaml:"isHidden"`
Properties ResourceProperties `json:"properties" yaml:"properties"`
Attributes map[string]interface{} `json:"attributes" yaml:"attributes"`
Expand All @@ -22,7 +22,7 @@ type DeviceResource struct {
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.2.0#/DeviceResource
type UpdateDeviceResource struct {
Description *string `json:"description"`
Name *string `json:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Name *string `json:"name" validate:"required,edgex-dto-none-empty-string"`
IsHidden *bool `json:"isHidden"`
}

Expand Down
2 changes: 1 addition & 1 deletion dtos/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Event struct {
Id string `json:"id" validate:"required,uuid"`
DeviceName string `json:"deviceName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
ProfileName string `json:"profileName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
SourceName string `json:"sourceName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
SourceName string `json:"sourceName" validate:"required"`
Origin int64 `json:"origin" validate:"required"`
Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"`
Tags Tags `json:"tags,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion dtos/reading.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type BaseReading struct {
Id string `json:"id,omitempty"`
Origin int64 `json:"origin" validate:"required"`
DeviceName string `json:"deviceName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
ResourceName string `json:"resourceName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
ResourceName string `json:"resourceName" validate:"required"`
ProfileName string `json:"profileName" validate:"required,edgex-dto-rfc3986-unreserved-chars"`
ValueType string `json:"valueType" validate:"required,edgex-dto-value-type"`
Units string `json:"units,omitempty"`
Expand Down
Loading

0 comments on commit abb2fde

Please sign in to comment.