diff --git a/v2/constants.go b/v2/constants.go index 7a88f7ee..7c0817ab 100644 --- a/v2/constants.go +++ b/v2/constants.go @@ -191,7 +191,7 @@ const ( Password = "Password" ) -// Constants for Addressable +// Constants for Address const ( // Type REST = "REST" diff --git a/v2/dtos/address.go b/v2/dtos/address.go new file mode 100644 index 00000000..524ff2a1 --- /dev/null +++ b/v2/dtos/address.go @@ -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 +} diff --git a/v2/dtos/address_test.go b/v2/dtos/address_test.go new file mode 100644 index 00000000..b3a59d82 --- /dev/null +++ b/v2/dtos/address_test.go @@ -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) + } + }) + } +} diff --git a/v2/dtos/addressable.go b/v2/dtos/addressable.go deleted file mode 100644 index 917b638e..00000000 --- a/v2/dtos/addressable.go +++ /dev/null @@ -1,98 +0,0 @@ -package dtos - -import ( - "github.com/edgexfoundry/go-mod-core-contracts/v2/v2" - "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos/common" - "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models" -) - -type Addressable struct { - common.Versionable `json:",inline"` - - RESTAddressable `json:",inline"` - MqttPubAddressable `json:",inline"` -} - -type BaseAddressable struct { - Type string `json:"type" validate:"oneof='REST' 'MQTT'"` - - Host string `json:"host"` - Port int `json:"port"` -} - -type RESTAddressable struct { - BaseAddressable `json:",inline"` - 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 MqttPubAddressable struct { - BaseAddressable `json:",inline"` - Publisher string `json:"publisher"` - Topic string `json:"topic"` - 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 ToAddressableModel(a Addressable) models.Addressable { - var addressable models.Addressable - - switch a.Type { - case v2.REST: - addressable = models.RESTAddressable{ - BaseAddressable: models.BaseAddressable(a.RESTAddressable.BaseAddressable), - Path: a.RESTAddressable.Path, - QueryParameters: a.RESTAddressable.QueryParameters, - HTTPMethod: a.RESTAddressable.HTTPMethod, - } - break - case v2.MQTT: - addressable = models.MqttPubAddressable{ - BaseAddressable: models.BaseAddressable(a.MqttPubAddressable.BaseAddressable), - Publisher: a.MqttPubAddressable.Publisher, - Topic: a.MqttPubAddressable.Topic, - QoS: a.QoS, - KeepAlive: a.KeepAlive, - Retained: a.Retained, - AutoReconnect: a.AutoReconnect, - ConnectTimeout: a.ConnectTimeout, - } - break - } - return addressable -} - -func FromAddressableModelToDTO(addressable models.Addressable) Addressable { - dto := Addressable{} - base := BaseAddressable(addressable.GetBaseAddressable()) - - switch a := addressable.(type) { - case models.RESTAddressable: - dto.Versionable = common.NewVersionable() - dto.RESTAddressable = RESTAddressable{ - Path: a.Path, - QueryParameters: a.QueryParameters, - HTTPMethod: a.HTTPMethod, - } - dto.RESTAddressable.BaseAddressable = base - break - case models.MqttPubAddressable: - dto.Versionable = common.NewVersionable() - dto.MqttPubAddressable = MqttPubAddressable{ - Publisher: a.Publisher, - Topic: a.Topic, - QoS: a.QoS, - KeepAlive: a.KeepAlive, - Retained: a.Retained, - AutoReconnect: a.AutoReconnect, - ConnectTimeout: a.ConnectTimeout, - } - dto.MqttPubAddressable.BaseAddressable = base - break - } - return dto -} diff --git a/v2/models/address.go b/v2/models/address.go new file mode 100644 index 00000000..5323df01 --- /dev/null +++ b/v2/models/address.go @@ -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 } diff --git a/v2/models/addressable.go b/v2/models/addressable.go deleted file mode 100644 index 46115fdd..00000000 --- a/v2/models/addressable.go +++ /dev/null @@ -1,39 +0,0 @@ -package models - -type Addressable interface { - GetBaseAddressable() BaseAddressable -} - -// BaseAddressable is a base struct contains the common fields, such as type, host, port, and so on. -type BaseAddressable struct { - // Type is used to identify the Addressable type, i.e., REST or MQTT - Type string - - // Common properties - Host string - Port int -} - -// RESTAddressable is a REST specific struct -type RESTAddressable struct { - BaseAddressable - Path string - QueryParameters string - HTTPMethod string -} - -func (a RESTAddressable) GetBaseAddressable() BaseAddressable { return a.BaseAddressable } - -// MqttPubAddressable is a MQTT specific struct -type MqttPubAddressable struct { - BaseAddressable - Publisher string - Topic string - QoS int - KeepAlive int - Retained bool - AutoReconnect bool - ConnectTimeout int -} - -func (a MqttPubAddressable) GetBaseAddressable() BaseAddressable { return a.BaseAddressable }