dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts@v1.0.2/dtos/deviceprofile.go (about)

     1  //
     2  // Copyright (C) 2020-2021 IOTech Ltd
     3  //
     4  // SPDX-License-Identifier: Apache-2.0
     5  
     6  package dtos
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/common"
    14  	edgexErrors "dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/errors"
    15  	"dev.azure.com/aidainnovazione0090/DeviceManager/_git/go-mod-core-contracts/models"
    16  )
    17  
    18  type DeviceProfile struct {
    19  	DBTimestamp            `json:",inline"`
    20  	DeviceProfileBasicInfo `json:",inline" yaml:",inline"`
    21  	DeviceResources        []DeviceResource `json:"deviceResources" yaml:"deviceResources" validate:"dive"`
    22  	DeviceCommands         []DeviceCommand  `json:"deviceCommands" yaml:"deviceCommands" validate:"dive"`
    23  }
    24  
    25  // Validate satisfies the Validator interface
    26  func (dp *DeviceProfile) Validate() error {
    27  	err := common.Validate(dp)
    28  	if err != nil {
    29  		// The DeviceProfileBasicInfo is the internal struct in Golang programming, not in the Profile model,
    30  		// so it should be hidden from the error messages.
    31  		err = errors.New(strings.ReplaceAll(err.Error(), ".DeviceProfileBasicInfo", ""))
    32  		return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, "Invalid DeviceProfile.", err)
    33  	}
    34  	return ValidateDeviceProfileDTO(*dp)
    35  }
    36  
    37  // UnmarshalYAML implements the Unmarshaler interface for the DeviceProfile type
    38  func (dp *DeviceProfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
    39  	var alias struct {
    40  		DBTimestamp
    41  		DeviceProfileBasicInfo `yaml:",inline"`
    42  		DeviceResources        []DeviceResource `yaml:"deviceResources"`
    43  		DeviceCommands         []DeviceCommand  `yaml:"deviceCommands"`
    44  	}
    45  	if err := unmarshal(&alias); err != nil {
    46  		return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, "failed to unmarshal request body as YAML.", err)
    47  	}
    48  	*dp = DeviceProfile(alias)
    49  
    50  	if err := dp.Validate(); err != nil {
    51  		return edgexErrors.NewCommonEdgeXWrapper(err)
    52  	}
    53  
    54  	// Normalize resource's value type
    55  	for i, resource := range dp.DeviceResources {
    56  		valueType, err := common.NormalizeValueType(resource.Properties.ValueType)
    57  		if err != nil {
    58  			return edgexErrors.NewCommonEdgeXWrapper(err)
    59  		}
    60  		dp.DeviceResources[i].Properties.ValueType = valueType
    61  	}
    62  	return nil
    63  }
    64  
    65  // ToDeviceProfileModel transforms the DeviceProfile DTO to the DeviceProfile model
    66  func ToDeviceProfileModel(deviceProfileDTO DeviceProfile) models.DeviceProfile {
    67  	return models.DeviceProfile{
    68  		DBTimestamp:     models.DBTimestamp(deviceProfileDTO.DBTimestamp),
    69  		Id:              deviceProfileDTO.Id,
    70  		Name:            deviceProfileDTO.Name,
    71  		Description:     deviceProfileDTO.Description,
    72  		Manufacturer:    deviceProfileDTO.Manufacturer,
    73  		Model:           deviceProfileDTO.Model,
    74  		Labels:          deviceProfileDTO.Labels,
    75  		DeviceResources: ToDeviceResourceModels(deviceProfileDTO.DeviceResources),
    76  		DeviceCommands:  ToDeviceCommandModels(deviceProfileDTO.DeviceCommands),
    77  	}
    78  }
    79  
    80  // FromDeviceProfileModelToDTO transforms the DeviceProfile Model to the DeviceProfile DTO
    81  func FromDeviceProfileModelToDTO(deviceProfile models.DeviceProfile) DeviceProfile {
    82  	return DeviceProfile{
    83  		DBTimestamp: DBTimestamp(deviceProfile.DBTimestamp),
    84  		DeviceProfileBasicInfo: DeviceProfileBasicInfo{
    85  			Id:           deviceProfile.Id,
    86  			Name:         deviceProfile.Name,
    87  			Description:  deviceProfile.Description,
    88  			Manufacturer: deviceProfile.Manufacturer,
    89  			Model:        deviceProfile.Model,
    90  			Labels:       deviceProfile.Labels,
    91  		},
    92  		DeviceResources: FromDeviceResourceModelsToDTOs(deviceProfile.DeviceResources),
    93  		DeviceCommands:  FromDeviceCommandModelsToDTOs(deviceProfile.DeviceCommands),
    94  	}
    95  }
    96  
    97  func ValidateDeviceProfileDTO(profile DeviceProfile) error {
    98  	// deviceResources validation
    99  	dupCheck := make(map[string]bool)
   100  	for _, resource := range profile.DeviceResources {
   101  		if resource.Properties.ValueType == common.ValueTypeBinary &&
   102  			strings.Contains(resource.Properties.ReadWrite, common.ReadWrite_W) {
   103  			return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, fmt.Sprintf("write permission not support %s value type for resource '%s'", common.ValueTypeBinary, resource.Name), nil)
   104  		}
   105  		// deviceResource name should not duplicated
   106  		if dupCheck[resource.Name] {
   107  			return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, fmt.Sprintf("device resource %s is duplicated", resource.Name), nil)
   108  		}
   109  		dupCheck[resource.Name] = true
   110  	}
   111  	// deviceCommands validation
   112  	dupCheck = make(map[string]bool)
   113  	for _, command := range profile.DeviceCommands {
   114  		// deviceCommand name should not duplicated
   115  		if dupCheck[command.Name] {
   116  			return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, fmt.Sprintf("device command %s is duplicated", command.Name), nil)
   117  		}
   118  		dupCheck[command.Name] = true
   119  
   120  		resourceOperations := command.ResourceOperations
   121  		for _, ro := range resourceOperations {
   122  			// ResourceOperations referenced in deviceCommands must exist
   123  			if !deviceResourcesContains(profile.DeviceResources, ro.DeviceResource) {
   124  				return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, fmt.Sprintf("device command's resource %s doesn't match any device resource", ro.DeviceResource), nil)
   125  			}
   126  			// Check the ReadWrite whether is align to the deviceResource
   127  			if !validReadWritePermission(profile.DeviceResources, ro.DeviceResource, command.ReadWrite) {
   128  				return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, fmt.Sprintf("device command's ReadWrite permission '%s' doesn't align the device resource", command.ReadWrite), nil)
   129  			}
   130  		}
   131  	}
   132  	return nil
   133  }
   134  
   135  func deviceResourcesContains(resources []DeviceResource, name string) bool {
   136  	contains := false
   137  	for _, resource := range resources {
   138  		if resource.Name == name {
   139  			contains = true
   140  			break
   141  		}
   142  	}
   143  	return contains
   144  }
   145  
   146  func validReadWritePermission(resources []DeviceResource, name string, readWrite string) bool {
   147  	valid := true
   148  	for _, resource := range resources {
   149  		if resource.Name == name {
   150  			if resource.Properties.ReadWrite != common.ReadWrite_RW && resource.Properties.ReadWrite != common.ReadWrite_WR &&
   151  				resource.Properties.ReadWrite != readWrite {
   152  				valid = false
   153  				break
   154  			}
   155  		}
   156  	}
   157  	return valid
   158  }