diff --git a/dtos/event.go b/dtos/event.go index e4c8db2e..8b267597 100644 --- a/dtos/event.go +++ b/dtos/event.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020-2021 IOTech Ltd +// Copyright (C) 2020-2023 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -7,8 +7,6 @@ package dtos import ( "encoding/xml" - "fmt" - "strings" "time" "github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/common" @@ -21,13 +19,13 @@ import ( // https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-data/2.1.0#/Event type Event struct { common.Versionable `json:",inline"` - 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"` - Origin int64 `json:"origin" validate:"required"` - Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"` - Tags map[string]interface{} `json:"tags,omitempty" xml:"-"` // Have to ignore since map not supported for XML + 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"` + Origin int64 `json:"origin" validate:"required"` + Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"` + Tags Tags `json:"tags,omitempty"` } // NewEvent creates and returns an initialized Event with no Readings @@ -93,19 +91,5 @@ func (e *Event) ToXML() (string, error) { return "", err } - // The Tags field is being ignore from XML Marshaling since maps are not supported. - // We have to provide our own marshaling of the Tags field if it is non-empty - if len(e.Tags) > 0 { - tagsXmlElements := []string{""} - // Since we change the tags value from string to interface{}, we need to write more complex func or use third-party lib to handle the marshaling - for key, value := range e.Tags { - tag := fmt.Sprintf("<%s>%s", key, value, key) - tagsXmlElements = append(tagsXmlElements, tag) - } - tagsXmlElements = append(tagsXmlElements, "") - tagsXml := strings.Join(tagsXmlElements, "") - eventXml = []byte(strings.Replace(string(eventXml), "", tagsXml+"", 1)) - } - return string(eventXml), nil } diff --git a/dtos/reading.go b/dtos/reading.go index 75d79e72..fdbc3aca 100644 --- a/dtos/reading.go +++ b/dtos/reading.go @@ -22,14 +22,14 @@ import ( // BaseReading and its properties are defined in the APIv2 specification: // https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-data/2.1.0#/BaseReading 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"` - 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"` - Tags map[string]any `json:"tags,omitempty"` + 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"` + 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"` + Tags Tags `json:"tags,omitempty"` BinaryReading `json:",inline" validate:"-"` SimpleReading `json:",inline" validate:"-"` ObjectReading `json:",inline" validate:"-"` diff --git a/dtos/tags.go b/dtos/tags.go new file mode 100644 index 00000000..01e10782 --- /dev/null +++ b/dtos/tags.go @@ -0,0 +1,41 @@ +// +// Copyright (C) 2023 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package dtos + +import "encoding/xml" + +type Tags map[string]any + +// MarshalXML fulfills the Marshaler interface for Tags field, which is being ignored +// from XML Marshaling since maps are not supported. We have to provide our own +// marshaling of the Tags field if it is non-empty. +func (t Tags) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if len(t) == 0 { + return nil + } + + err := e.EncodeToken(start) + if err != nil { + return err + } + + for k, v := range t { + xmlMapEntry := struct { + XMLName xml.Name + Value any `xml:",chardata"` + }{ + XMLName: xml.Name{Local: k}, + Value: v, + } + + err = e.Encode(xmlMapEntry) + if err != nil { + return err + } + } + + return e.EncodeToken(start.End()) +} diff --git a/dtos/tags_test.go b/dtos/tags_test.go new file mode 100644 index 00000000..8d380426 --- /dev/null +++ b/dtos/tags_test.go @@ -0,0 +1,35 @@ +// +// Copyright (C) 2023 IOTech Ltd +// +// SPDX-License-Identifier: Apache-2.0 + +package dtos + +import ( + "encoding/xml" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTags_MarshalXML(t *testing.T) { + testTags := Tags{ + "String": "value", + "Numeric": 123, + "Bool": false, + } + + xml, err := xml.Marshal(testTags) + require.NoError(t, err) + + contains := []string{ + "value", + "123", + "false"} + + for _, v := range contains { + ok := strings.Contains(string(xml), v) + require.True(t, ok) + } +}