Skip to content

Commit

Permalink
feat: Create a common Address struct for V2 API (#525)
Browse files Browse the repository at this point in the history
* feat: Create a common Addressable struct for V2 API

Create a common Addressable struct for different endpoint protocols

Signed-off-by: weichou <[email protected]>

* feat: Modify MQTT Addressable

Rename and add fields

Signed-off-by: weichou <[email protected]>

* refactor: Refactor Addressable and add test

- Rename Addressable to Address
- Remove BaseAddress DTO because the json unmarshal func can't identify repeat fields
- Add test for json unmarshalling and validation

Close #523

Signed-off-by: weichou <[email protected]>

* refactor: Rename MqttPubAddress to MQTTPubAddress

Fix #523

Signed-off-by: weichou <[email protected]>
  • Loading branch information
weichou1229 authored Mar 8, 2021
1 parent 3caf778 commit eae89da
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 0 deletions.
7 changes: 7 additions & 0 deletions v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,10 @@ const (
Primary = "Primary"
Password = "Password"
)

// Constants for Address
const (
// Type
REST = "REST"
MQTT = "MQTT"
)
123 changes: 123 additions & 0 deletions v2/dtos/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package dtos

import (
"github.com/edgexfoundry/go-mod-core-contracts/v2/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
)

type Address struct {
Type string `json:"type" validate:"oneof='REST' 'MQTT'"`

Host string `json:"host" validate:"required"`
Port int `json:"port" validate:"required"`

RESTAddress `json:",inline" validate:"-"`
MQTTPubAddress `json:",inline" validate:"-"`
}

// Validate satisfies the Validator interface
func (a *Address) Validate() error {
err := v2.Validate(a)
if err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "invalid Address.", err)
}
switch a.Type {
case v2.REST:
err = v2.Validate(a.RESTAddress)
if err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "invalid RESTAddress.", err)
}
break
case v2.MQTT:
err = v2.Validate(a.MQTTPubAddress)
if err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "invalid MQTTPubAddress.", err)
}
break
}
return nil
}

type RESTAddress struct {
Path string `json:"path,omitempty"`
QueryParameters string `json:"queryParameters,omitempty"`
HTTPMethod string `json:"httpMethod" validate:"required,oneof='GET' 'HEAD' 'POST' 'PUT' 'DELETE' 'TRACE' 'CONNECT'"`
}

type MQTTPubAddress struct {
Publisher string `json:"publisher" validate:"required"`
Topic string `json:"topic" validate:"required"`
QoS int `json:"qos,omitempty"`
KeepAlive int `json:"keepAlive,omitempty"`
Retained bool `json:"retained,omitempty"`
AutoReconnect bool `json:"autoReconnect,omitempty"`
ConnectTimeout int `json:"connectTimeout,omitempty"`
}

func ToAddressModel(a Address) models.Address {
var address models.Address

switch a.Type {
case v2.REST:
address = models.RESTAddress{
BaseAddress: models.BaseAddress{
Type: a.Type, Host: a.Host, Port: a.Port,
},
Path: a.RESTAddress.Path,
QueryParameters: a.RESTAddress.QueryParameters,
HTTPMethod: a.RESTAddress.HTTPMethod,
}
break
case v2.MQTT:
address = models.MQTTPubAddress{
BaseAddress: models.BaseAddress{
Type: a.Type, Host: a.Host, Port: a.Port,
},
Publisher: a.MQTTPubAddress.Publisher,
Topic: a.MQTTPubAddress.Topic,
QoS: a.QoS,
KeepAlive: a.KeepAlive,
Retained: a.Retained,
AutoReconnect: a.AutoReconnect,
ConnectTimeout: a.ConnectTimeout,
}
break
}
return address
}

func FromAddressModelToDTO(address models.Address) Address {
dto := Address{
Type: address.GetBaseAddress().Type,
Host: address.GetBaseAddress().Host,
Port: address.GetBaseAddress().Port,
}

switch a := address.(type) {
case models.RESTAddress:
dto.RESTAddress = RESTAddress{
Path: a.Path,
QueryParameters: a.QueryParameters,
HTTPMethod: a.HTTPMethod,
}
break
case models.MQTTPubAddress:
dto.MQTTPubAddress = MQTTPubAddress{
Publisher: a.Publisher,
Topic: a.Topic,
QoS: a.QoS,
KeepAlive: a.KeepAlive,
Retained: a.Retained,
AutoReconnect: a.AutoReconnect,
ConnectTimeout: a.ConnectTimeout,
}
break
}
return dto
}
122 changes: 122 additions & 0 deletions v2/dtos/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package dtos

import (
"encoding/json"
"fmt"
"testing"

"github.com/edgexfoundry/go-mod-core-contracts/v2/v2"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
testHost = "testHost"
testPort = 123
testPath = "testPath"
testQueryParameters = "testQueryParameters"
testHTTPMethod = "GET"
testPublisher = "testPublisher"
testTopic = "testTopic"
)

var testRESTAddress = Address{
Type: v2.REST,
Host: testHost,
Port: testPort,
RESTAddress: RESTAddress{
Path: testPath,
QueryParameters: testQueryParameters,
HTTPMethod: testHTTPMethod,
},
}

var testMQTTPubAddress = Address{
Type: v2.MQTT,
Host: testHost,
Port: testPort,
MQTTPubAddress: MQTTPubAddress{
Publisher: testPublisher,
Topic: testTopic,
},
}

func TestAddress_UnmarshalJSON(t *testing.T) {
restJsonStr := fmt.Sprintf(
`{"type":"%s","host":"%s","port":%d,"path":"%s","queryParameters":"%s","httpMethod":"%s"}`,
testRESTAddress.Type, testRESTAddress.Host, testRESTAddress.Port,
testRESTAddress.Path, testRESTAddress.QueryParameters, testRESTAddress.HTTPMethod,
)
mqttJsonStr := fmt.Sprintf(
`{"type":"%s","host":"%s","port":%d,"Publisher":"%s","Topic":"%s"}`,
testMQTTPubAddress.Type, testMQTTPubAddress.Host, testMQTTPubAddress.Port,
testMQTTPubAddress.Publisher, testMQTTPubAddress.Topic,
)

type args struct {
data []byte
}
tests := []struct {
name string
expected Address
data []byte
wantErr bool
}{
{"unmarshal RESTAddress with success", testRESTAddress, []byte(restJsonStr), false},
{"unmarshal MQTTPubAddress with success", testMQTTPubAddress, []byte(mqttJsonStr), false},
{"unmarshal invalid Address, empty data", Address{}, []byte{}, true},
{"unmarshal invalid Address, string data", Address{}, []byte("Invalid address"), true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result Address
err := json.Unmarshal(tt.data, &result)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected, result, "Unmarshal did not result in expected Address.", err)
}
})
}
}

func TestAddress_Validate(t *testing.T) {
validRest := testRESTAddress
noRestHttpMethod := testRESTAddress
noRestHttpMethod.HTTPMethod = ""

validMQTT := testMQTTPubAddress
noMQTTPublisher := testMQTTPubAddress
noMQTTPublisher.Publisher = ""
noMQTTTopic := testMQTTPubAddress
noMQTTTopic.Topic = ""
tests := []struct {
name string
dto Address
expectError bool
}{
{"valid RESTAddress", validRest, false},
{"invalid RESTAddress, no HTTP method", noRestHttpMethod, true},
{"valid MQTTPubAddress", validMQTT, false},
{"invalid MQTTPubAddress, no MQTT publisher", noMQTTPublisher, true},
{"invalid MQTTPubAddress, no MQTT Topic", noMQTTTopic, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.dto.Validate()
if tt.expectError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
44 changes: 44 additions & 0 deletions v2/models/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Copyright (C) 2021 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package models

type Address interface {
GetBaseAddress() BaseAddress
}

// BaseAddress is a base struct contains the common fields, such as type, host, port, and so on.
type BaseAddress struct {
// Type is used to identify the Address type, i.e., REST or MQTT
Type string

// Common properties
Host string
Port int
}

// RESTAddress is a REST specific struct
type RESTAddress struct {
BaseAddress
Path string
QueryParameters string
HTTPMethod string
}

func (a RESTAddress) GetBaseAddress() BaseAddress { return a.BaseAddress }

// MQTTPubAddress is a MQTT specific struct
type MQTTPubAddress struct {
BaseAddress
Publisher string
Topic string
QoS int
KeepAlive int
Retained bool
AutoReconnect bool
ConnectTimeout int
}

func (a MQTTPubAddress) GetBaseAddress() BaseAddress { return a.BaseAddress }

0 comments on commit eae89da

Please sign in to comment.