Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement the re-designed device profile model #540

Merged
merged 2 commits into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions v2/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,10 @@ const (
REST = "REST"
MQTT = "MQTT"
)

// Constants for DeviceProfile
const (
ReadWrite_R = "R"
ReadWrite_W = "W"
ReadWrite_RW = "RW"
)
52 changes: 0 additions & 52 deletions v2/dtos/command.go

This file was deleted.

47 changes: 20 additions & 27 deletions v2/dtos/deviceCommand.go → v2/dtos/devicecommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,23 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
// DeviceCommand and its properties are defined in the APIv2 specification:
// https://app.swaggerhub.com/apis-docs/EdgeXFoundry1/core-metadata/2.x#/DeviceCommand
type DeviceCommand struct {
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
Get []ResourceOperation `json:"get,omitempty" yaml:"get,omitempty" validate:"required_without=Set"`
Set []ResourceOperation `json:"set,omitempty" yaml:"set,omitempty" validate:"required_without=Get"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
IsHidden bool `json:"isHidden,omitempty" yaml:"isHidden,omitempty"`
ReadWrite string `json:"readWrite" yaml:"readWrite" validate:"required,oneof='R' 'W' 'RW'"`
ResourceOperations []ResourceOperation `json:"resourceOperations" yaml:"resourceOperations" validate:"gt=0,dive"`
}

// ToDeviceCommandModel transforms the DeviceCommand DTO to the DeviceCommand model
func ToDeviceCommandModel(p DeviceCommand) models.DeviceCommand {
getResourceOperations := make([]models.ResourceOperation, len(p.Get))
for i, ro := range p.Get {
getResourceOperations[i] = ToResourceOperationModel(ro)
func ToDeviceCommandModel(dto DeviceCommand) models.DeviceCommand {
resourceOperations := make([]models.ResourceOperation, len(dto.ResourceOperations))
for i, ro := range dto.ResourceOperations {
resourceOperations[i] = ToResourceOperationModel(ro)
}
setResourceOperations := make([]models.ResourceOperation, len(p.Set))
for i, ro := range p.Set {
setResourceOperations[i] = ToResourceOperationModel(ro)
}

return models.DeviceCommand{
Name: p.Name,
Get: getResourceOperations,
Set: setResourceOperations,
Name: dto.Name,
IsHidden: dto.IsHidden,
ReadWrite: dto.ReadWrite,
ResourceOperations: resourceOperations,
}
}

Expand All @@ -43,20 +40,16 @@ func ToDeviceCommandModels(deviceCommandDTOs []DeviceCommand) []models.DeviceCom
}

// FromDeviceCommandModelToDTO transforms the DeviceCommand model to the DeviceCommand DTO
func FromDeviceCommandModelToDTO(p models.DeviceCommand) DeviceCommand {
getResourceOperations := make([]ResourceOperation, len(p.Get))
for i, ro := range p.Get {
getResourceOperations[i] = FromResourceOperationModelToDTO(ro)
func FromDeviceCommandModelToDTO(d models.DeviceCommand) DeviceCommand {
resourceOperations := make([]ResourceOperation, len(d.ResourceOperations))
for i, ro := range d.ResourceOperations {
resourceOperations[i] = FromResourceOperationModelToDTO(ro)
}
setResourceOperations := make([]ResourceOperation, len(p.Set))
for i, ro := range p.Set {
setResourceOperations[i] = FromResourceOperationModelToDTO(ro)
}

return DeviceCommand{
Name: p.Name,
Get: getResourceOperations,
Set: setResourceOperations,
Name: d.Name,
IsHidden: d.IsHidden,
ReadWrite: d.ReadWrite,
ResourceOperations: resourceOperations,
}
}

Expand Down
50 changes: 16 additions & 34 deletions v2/dtos/deviceprofile.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type DeviceProfile struct {
Labels []string `json:"labels,omitempty" yaml:"labels,flow,omitempty"`
DeviceResources []DeviceResource `json:"deviceResources" yaml:"deviceResources" validate:"required,gt=0,dive"`
DeviceCommands []DeviceCommand `json:"deviceCommands,omitempty" yaml:"deviceCommands,omitempty" validate:"dive"`
CoreCommands []Command `json:"coreCommands,omitempty" yaml:"coreCommands,omitempty" validate:"dive"`
}

// Validate satisfies the Validator interface
Expand All @@ -50,7 +49,6 @@ func (dp *DeviceProfile) UnmarshalYAML(unmarshal func(interface{}) error) error
Labels []string `yaml:"labels"`
DeviceResources []DeviceResource `yaml:"deviceResources"`
DeviceCommands []DeviceCommand `yaml:"deviceCommands"`
CoreCommands []Command `yaml:"coreCommands"`
}
if err := unmarshal(&alias); err != nil {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "failed to unmarshal request body as YAML.", err)
Expand Down Expand Up @@ -83,7 +81,6 @@ func ToDeviceProfileModel(deviceProfileDTO DeviceProfile) models.DeviceProfile {
Labels: deviceProfileDTO.Labels,
DeviceResources: ToDeviceResourceModels(deviceProfileDTO.DeviceResources),
DeviceCommands: ToDeviceCommandModels(deviceProfileDTO.DeviceCommands),
CoreCommands: ToCommandModels(deviceProfileDTO.CoreCommands),
}
}

Expand All @@ -99,7 +96,6 @@ func FromDeviceProfileModelToDTO(deviceProfile models.DeviceProfile) DeviceProfi
Labels: deviceProfile.Labels,
DeviceResources: FromDeviceResourceModelsToDTOs(deviceProfile.DeviceResources),
DeviceCommands: FromDeviceCommandModelsToDTOs(deviceProfile.DeviceCommands),
CoreCommands: FromCommandModelsToDTOs(deviceProfile.CoreCommands),
}
}

Expand All @@ -122,35 +118,18 @@ func ValidateDeviceProfileDTO(profile DeviceProfile) error {
}
dupCheck[command.Name] = true

// deviceResources referenced in deviceCommands must exist
getCommands := command.Get
for _, getCommand := range getCommands {
if !deviceResourcesContains(profile.DeviceResources, getCommand.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's Get resource %s doesn't match any deivce resource", getCommand.DeviceResource), nil)
resourceOperations := command.ResourceOperations
for _, ro := range resourceOperations {
// ResourceOperations referenced in deviceCommands must exist
if !deviceResourcesContains(profile.DeviceResources, ro.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's resource %s doesn't match any deivce resource", ro.DeviceResource), nil)
}
}
setCommands := command.Set
for _, setCommand := range setCommands {
if !deviceResourcesContains(profile.DeviceResources, setCommand.DeviceResource) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's Set resource %s doesn't match any deivce resource", setCommand.DeviceResource), nil)
// Check the ReadWrite whether is align to the deviceResource
if !validReadWritePermission(profile.DeviceResources, ro.DeviceResource, command.ReadWrite) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("device command's ReadWrite permission '%s' doesn't align the deivce resource", command.ReadWrite), nil)
}
}
}
// coreCommands validation
dupCheck = make(map[string]bool)
for _, command := range profile.CoreCommands {
// coreCommand name should not duplicated
if dupCheck[command.Name] {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("core command %s is duplicated", command.Name), nil)
}
dupCheck[command.Name] = true

// coreCommands name should match the one of deviceResources and deviceCommands
if !deviceCommandsContains(profile.DeviceCommands, command.Name) &&
!deviceResourcesContains(profile.DeviceResources, command.Name) {
return errors.NewCommonEdgeX(errors.KindContractInvalid, fmt.Sprintf("core command %s doesn't match any deivce command or resource", command.Name), nil)
}
}
return nil
}

Expand All @@ -165,13 +144,16 @@ func deviceResourcesContains(resources []DeviceResource, name string) bool {
return contains
}

func deviceCommandsContains(resources []DeviceCommand, name string) bool {
contains := false
func validReadWritePermission(resources []DeviceResource, name string, readWrite string) bool {
valid := true
for _, resource := range resources {
if resource.Name == name {
contains = true
break
if resource.Properties.ReadWrite != v2.ReadWrite_RW &&
resource.Properties.ReadWrite != readWrite {
valid = false
break
}
}
}
return contains
return valid
}
57 changes: 18 additions & 39 deletions v2/dtos/deviceprofile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,16 @@ var testDeviceProfile = models.DeviceProfile{
Attributes: testAttributes,
Properties: models.PropertyValue{
ValueType: v2.ValueTypeInt16,
ReadWrite: "RW",
ReadWrite: v2.ReadWrite_RW,
},
}},
DeviceCommands: []models.DeviceCommand{{
Name: TestDeviceCommandName,
Get: []models.ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
Set: []models.ResourceOperation{{
Name: TestDeviceCommandName,
ReadWrite: v2.ReadWrite_RW,
ResourceOperations: []models.ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
}},
CoreCommands: []models.Command{{
Name: TestDeviceCommandName,
Get: true,
Set: true,
}},
}

func profileData() DeviceProfile {
Expand All @@ -69,22 +62,15 @@ func profileData() DeviceProfile {
Attributes: testAttributes,
Properties: PropertyValue{
ValueType: v2.ValueTypeInt16,
ReadWrite: "RW",
ReadWrite: v2.ReadWrite_RW,
},
}},
DeviceCommands: []DeviceCommand{{
Name: TestDeviceCommandName,
Get: []ResourceOperation{{
Name: TestDeviceCommandName,
ReadWrite: v2.ReadWrite_RW,
ResourceOperations: []ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
Set: []ResourceOperation{{
DeviceResource: TestDeviceResourceName,
}},
}},
CoreCommands: []Command{{
Name: TestDeviceCommandName,
Get: true,
Set: true,
}},
}
}
Expand All @@ -102,18 +88,11 @@ func TestValidateDeviceProfileDTO(t *testing.T) {
duplicatedDeviceCommand := profileData()
duplicatedDeviceCommand.DeviceCommands = append(
duplicatedDeviceCommand.DeviceCommands, DeviceCommand{Name: TestDeviceCommandName})
duplicatedCoreCommand := profileData()
duplicatedCoreCommand.CoreCommands = append(
duplicatedCoreCommand.CoreCommands, Command{Name: TestDeviceCommandName})
mismatchedCoreCommand := profileData()
mismatchedCoreCommand.CoreCommands = append(
mismatchedCoreCommand.CoreCommands, Command{Name: "missMatchedCoreCommand"})
mismatchedGetResource := profileData()
mismatchedGetResource.DeviceCommands[0].Get = append(
mismatchedGetResource.DeviceCommands[0].Get, ResourceOperation{DeviceResource: "missMatchedResource"})
mismatchedSetResource := profileData()
mismatchedSetResource.DeviceCommands[0].Set = append(
mismatchedSetResource.DeviceCommands[0].Set, ResourceOperation{DeviceResource: "missMatchedResource"})
mismatchedResource := profileData()
mismatchedResource.DeviceCommands[0].ResourceOperations = append(
mismatchedResource.DeviceCommands[0].ResourceOperations, ResourceOperation{DeviceResource: "missMatchedResource"})
invalidReadWrite := profileData()
invalidReadWrite.DeviceResources[0].Properties.ReadWrite = v2.ReadWrite_R

tests := []struct {
name string
Expand All @@ -123,10 +102,8 @@ func TestValidateDeviceProfileDTO(t *testing.T) {
{"valid device profile", valid, false},
{"duplicated device resource", duplicatedDeviceResource, true},
{"duplicated device command", duplicatedDeviceCommand, true},
{"duplicated core command", duplicatedCoreCommand, true},
{"mismatched core command", mismatchedCoreCommand, true},
{"mismatched Get resource", mismatchedGetResource, true},
{"mismatched Set resource", mismatchedSetResource, true},
{"mismatched resource", mismatchedResource, true},
{"invalid ReadWrite permission", invalidReadWrite, true},
}

for _, tt := range tests {
Expand All @@ -153,7 +130,8 @@ deviceResources:
deviceCommands:
-
name: "GenerateDeviceValue_Boolean_RW"
get:
readWrite: "RW"
resourceOperations:
- { deviceResource: "DeviceValue_Boolean_RW" }
`
inValid := `
Expand All @@ -166,6 +144,7 @@ deviceResources:
deviceCommands:
-
name: "GenerateDeviceValue_Boolean_RW"
readWrite: "RW",
get:
- { deviceResource: "DeviceValue_Boolean_RW" }
`
Expand Down
3 changes: 3 additions & 0 deletions v2/dtos/deviceresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models"
type DeviceResource struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"`
IsHidden bool `json:"isHidden,omitempty" yaml:"isHidden,omitempty"`
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
Properties PropertyValue `json:"properties,omitempty" yaml:"properties,omitempty"`
Attributes map[string]string `json:"attributes,omitempty" yaml:"attributes,omitempty"`
Expand All @@ -22,6 +23,7 @@ func ToDeviceResourceModel(d DeviceResource) models.DeviceResource {
return models.DeviceResource{
Description: d.Description,
Name: d.Name,
IsHidden: d.IsHidden,
Tag: d.Tag,
Properties: ToPropertyValueModel(d.Properties),
Attributes: d.Attributes,
Expand All @@ -42,6 +44,7 @@ func FromDeviceResourceModelToDTO(d models.DeviceResource) DeviceResource {
return DeviceResource{
Description: d.Description,
Name: d.Name,
IsHidden: d.IsHidden,
Tag: d.Tag,
Properties: FromPropertyValueModelToDTO(d.Properties),
Attributes: d.Attributes,
Expand Down
Loading