Skip to content

Commit

Permalink
refactor: Refactor Addressable and add test
Browse files Browse the repository at this point in the history
- 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 <weichou1229@gmail.com>
  • Loading branch information
weichou1229 committed Mar 5, 2021
1 parent 827fd73 commit 11ddd03
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 138 deletions.
2 changes: 1 addition & 1 deletion v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ const (
Password = "Password"
)

// Constants for Addressable
// Constants for Address
const (
// Type
REST = "REST"
Expand Down
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)
}
})
}
}
98 changes: 0 additions & 98 deletions v2/dtos/addressable.go

This file was deleted.

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 }
Loading

0 comments on commit 11ddd03

Please sign in to comment.