From b4b29eca62c6eabc141589f69e0eaa5ee2c7d928 Mon Sep 17 00:00:00 2001 From: Ginny Guan Date: Tue, 19 Nov 2024 17:54:01 +0800 Subject: [PATCH] feat: Add more extendable fields to dtos/models for v4 close #951 Signed-off-by: Ginny Guan --- clients/http/deviceservicecommand_test.go | 13 ++ clients/http/notification.go | 53 +++++- clients/http/notification_test.go | 45 ++++- .../interfaces/mocks/NotificationClient.go | 180 +++++++++++++----- clients/interfaces/notification.go | 26 ++- clients/logger/logger.go | 2 +- common/constants.go | 5 +- common/utils.go | 25 +++ common/validator.go | 39 +++- dtos/autoevent.go | 2 +- dtos/const_test.go | 3 + dtos/deviceprofile.go | 7 + dtos/deviceprofile_test.go | 2 + dtos/deviceprofilebasicinfo.go | 12 +- dtos/deviceresource.go | 22 ++- dtos/deviceservice.go | 29 +-- dtos/deviceservice_test.go | 1 + dtos/event.go | 2 +- dtos/event_test.go | 15 +- dtos/notification.go | 43 +++-- dtos/notification_test.go | 1 + dtos/reading.go | 2 +- dtos/reading_test.go | 29 +++ dtos/requests/const_test.go | 1 - dtos/requests/device_test.go | 12 ++ dtos/requests/deviceprofile_test.go | 2 + dtos/requests/deviceservice.go | 3 + dtos/requests/deviceservice_test.go | 7 +- dtos/requests/notification.go | 37 ++++ dtos/requests/notification_test.go | 67 ++++++- dtos/requests/operation.go | 47 ----- dtos/requests/operation_test.go | 92 --------- dtos/requests/schedulejob.go | 2 +- dtos/resourceoperation.go | 4 +- dtos/resourceproperties.go | 22 +-- models/deviceprofile.go | 3 +- models/deviceservice.go | 3 +- models/notification.go | 19 +- 38 files changed, 594 insertions(+), 285 deletions(-) delete mode 100644 dtos/requests/operation.go delete mode 100644 dtos/requests/operation_test.go diff --git a/clients/http/deviceservicecommand_test.go b/clients/http/deviceservicecommand_test.go index 431d11ec..f86d4e2a 100644 --- a/clients/http/deviceservicecommand_test.go +++ b/clients/http/deviceservicecommand_test.go @@ -89,6 +89,19 @@ func TestSetCommandWithObject(t *testing.T) { assert.Equal(t, requestId, res.RequestId) } +func TestDiscovery(t *testing.T) { + requestId := uuid.New().String() + expectedResponse := dtoCommon.NewBaseResponse(requestId, "", http.StatusAccepted) + ts := newTestServer(http.MethodPost, common.ApiDiscoveryRoute, expectedResponse) + defer ts.Close() + + client := NewDeviceServiceCommandClient(NewNullAuthenticationInjector(), false) + res, err := client.Discovery(context.Background(), ts.URL) + + require.NoError(t, err) + assert.Equal(t, requestId, res.RequestId) +} + func TestProfileScan(t *testing.T) { requestId := uuid.New().String() expectedResponse := dtoCommon.NewBaseResponse(requestId, "", http.StatusAccepted) diff --git a/clients/http/notification.go b/clients/http/notification.go index 4c7380ae..e5c6b269 100644 --- a/clients/http/notification.go +++ b/clients/http/notification.go @@ -11,6 +11,7 @@ import ( "net/url" "path" "strconv" + "strings" "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/http/utils" "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/interfaces" @@ -66,11 +67,12 @@ func (client *NotificationClient) DeleteNotificationById(ctx context.Context, id } // NotificationsByCategory queries notifications with category, offset and limit -func (client *NotificationClient) NotificationsByCategory(ctx context.Context, category string, offset int, limit int) (res responses.MultiNotificationsResponse, err errors.EdgeX) { +func (client *NotificationClient) NotificationsByCategory(ctx context.Context, category string, offset int, limit int, ack string) (res responses.MultiNotificationsResponse, err errors.EdgeX) { requestPath := path.Join(common.ApiNotificationRoute, common.Category, category) requestParams := url.Values{} requestParams.Set(common.Offset, strconv.Itoa(offset)) requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams, client.authInjector) if err != nil { return res, errors.NewCommonEdgeXWrapper(err) @@ -79,11 +81,12 @@ func (client *NotificationClient) NotificationsByCategory(ctx context.Context, c } // NotificationsByLabel queries notifications with label, offset and limit -func (client *NotificationClient) NotificationsByLabel(ctx context.Context, label string, offset int, limit int) (res responses.MultiNotificationsResponse, err errors.EdgeX) { +func (client *NotificationClient) NotificationsByLabel(ctx context.Context, label string, offset int, limit int, ack string) (res responses.MultiNotificationsResponse, err errors.EdgeX) { requestPath := path.Join(common.ApiNotificationRoute, common.Label, label) requestParams := url.Values{} requestParams.Set(common.Offset, strconv.Itoa(offset)) requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams, client.authInjector) if err != nil { return res, errors.NewCommonEdgeXWrapper(err) @@ -92,11 +95,12 @@ func (client *NotificationClient) NotificationsByLabel(ctx context.Context, labe } // NotificationsByStatus queries notifications with status, offset and limit -func (client *NotificationClient) NotificationsByStatus(ctx context.Context, status string, offset int, limit int) (res responses.MultiNotificationsResponse, err errors.EdgeX) { +func (client *NotificationClient) NotificationsByStatus(ctx context.Context, status string, offset int, limit int, ack string) (res responses.MultiNotificationsResponse, err errors.EdgeX) { requestPath := path.Join(common.ApiNotificationRoute, common.Status, status) requestParams := url.Values{} requestParams.Set(common.Offset, strconv.Itoa(offset)) requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams, client.authInjector) if err != nil { return res, errors.NewCommonEdgeXWrapper(err) @@ -105,11 +109,12 @@ func (client *NotificationClient) NotificationsByStatus(ctx context.Context, sta } // NotificationsByTimeRange query notifications with time range, offset and limit -func (client *NotificationClient) NotificationsByTimeRange(ctx context.Context, start int64, end int64, offset int, limit int) (res responses.MultiNotificationsResponse, err errors.EdgeX) { +func (client *NotificationClient) NotificationsByTimeRange(ctx context.Context, start int64, end int64, offset int, limit int, ack string) (res responses.MultiNotificationsResponse, err errors.EdgeX) { requestPath := path.Join(common.ApiNotificationRoute, common.Start, strconv.FormatInt(start, 10), common.End, strconv.FormatInt(end, 10)) requestParams := url.Values{} requestParams.Set(common.Offset, strconv.Itoa(offset)) requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams, client.authInjector) if err != nil { return res, errors.NewCommonEdgeXWrapper(err) @@ -118,11 +123,12 @@ func (client *NotificationClient) NotificationsByTimeRange(ctx context.Context, } // NotificationsBySubscriptionName query notifications with subscriptionName, offset and limit -func (client *NotificationClient) NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int) (res responses.MultiNotificationsResponse, err errors.EdgeX) { +func (client *NotificationClient) NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int, ack string) (res responses.MultiNotificationsResponse, err errors.EdgeX) { requestPath := path.Join(common.ApiNotificationRoute, common.Subscription, common.Name, subscriptionName) requestParams := url.Values{} requestParams.Set(common.Offset, strconv.Itoa(offset)) requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) err = utils.GetRequest(ctx, &res, client.baseUrl, requestPath, requestParams, client.authInjector) if err != nil { return res, errors.NewCommonEdgeXWrapper(err) @@ -161,3 +167,40 @@ func (client *NotificationClient) DeleteProcessedNotificationsByAge(ctx context. } return res, nil } + +// NotificationsByQueryConditions queries notifications with offset, limit, acknowledgement status, category and time range +func (client *NotificationClient) NotificationsByQueryConditions(ctx context.Context, offset, limit int, ack string, conditionReq requests.GetNotificationRequest) (res responses.MultiNotificationsResponse, err errors.EdgeX) { + requestParams := url.Values{} + requestParams.Set(common.Offset, strconv.Itoa(offset)) + requestParams.Set(common.Limit, strconv.Itoa(limit)) + requestParams.Set(common.Ack, ack) + err = utils.GetRequestWithBodyRawData(ctx, &res, client.baseUrl, common.ApiNotificationRoute, requestParams, conditionReq, client.authInjector) + if err != nil { + return res, errors.NewCommonEdgeXWrapper(err) + } + return res, nil +} + +// DeleteNotificationByIds deletes notifications by ids +func (client *NotificationClient) DeleteNotificationByIds(ctx context.Context, ids []string) (res dtoCommon.BaseResponse, err errors.EdgeX) { + path := utils.EscapeAndJoinPath(common.ApiNotificationRoute, common.Ids, strings.Join(ids, common.CommaSeparator)) + err = utils.DeleteRequest(ctx, &res, client.baseUrl, path, client.authInjector) + if err != nil { + return res, errors.NewCommonEdgeXWrapper(err) + } + return res, nil +} + +// UpdateNotificationAckStatusByIds updates existing notification's acknowledgement status +func (client *NotificationClient) UpdateNotificationAckStatusByIds(ctx context.Context, ack bool, ids []string) (res dtoCommon.BaseResponse, err errors.EdgeX) { + pathAck := common.Unacknowledge + if ack { + pathAck = common.Acknowledge + } + path := utils.EscapeAndJoinPath(common.ApiNotificationRoute, pathAck, common.Ids, strings.Join(ids, common.CommaSeparator)) + err = utils.PutRequest(ctx, &res, client.baseUrl, path, nil, nil, client.authInjector) + if err != nil { + return res, errors.NewCommonEdgeXWrapper(err) + } + return res, nil +} diff --git a/clients/http/notification_test.go b/clients/http/notification_test.go index a5b52323..af6f0974 100644 --- a/clients/http/notification_test.go +++ b/clients/http/notification_test.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2021 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // Copyright (C) 2023 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 @@ -11,8 +11,10 @@ import ( "net/http" "path" "strconv" + "strings" "testing" + "github.com/edgexfoundry/go-mod-core-contracts/v4/clients/http/utils" "github.com/edgexfoundry/go-mod-core-contracts/v4/common" "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos" dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common" @@ -61,7 +63,7 @@ func TestNotificationClient_NotificationsByCategory(t *testing.T) { ts := newTestServer(http.MethodGet, urlPath, responses.MultiNotificationsResponse{}) defer ts.Close() client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) - res, err := client.NotificationsByCategory(context.Background(), category, 0, 10) + res, err := client.NotificationsByCategory(context.Background(), category, 0, 10, "") require.NoError(t, err) require.IsType(t, responses.MultiNotificationsResponse{}, res) } @@ -72,7 +74,7 @@ func TestNotificationClient_NotificationsByLabel(t *testing.T) { ts := newTestServer(http.MethodGet, urlPath, responses.MultiNotificationsResponse{}) defer ts.Close() client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) - res, err := client.NotificationsByLabel(context.Background(), label, 0, 10) + res, err := client.NotificationsByLabel(context.Background(), label, 0, 10, "") require.NoError(t, err) require.IsType(t, responses.MultiNotificationsResponse{}, res) } @@ -83,7 +85,7 @@ func TestNotificationClient_NotificationsByStatus(t *testing.T) { ts := newTestServer(http.MethodGet, urlPath, responses.MultiNotificationsResponse{}) defer ts.Close() client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) - res, err := client.NotificationsByStatus(context.Background(), status, 0, 10) + res, err := client.NotificationsByStatus(context.Background(), status, 0, 10, "") require.NoError(t, err) require.IsType(t, responses.MultiNotificationsResponse{}, res) } @@ -94,7 +96,7 @@ func TestNotificationClient_NotificationsBySubscriptionName(t *testing.T) { ts := newTestServer(http.MethodGet, urlPath, responses.MultiNotificationsResponse{}) defer ts.Close() client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) - res, err := client.NotificationsBySubscriptionName(context.Background(), subscriptionName, 0, 10) + res, err := client.NotificationsBySubscriptionName(context.Background(), subscriptionName, 0, 10, "") require.NoError(t, err) require.IsType(t, responses.MultiNotificationsResponse{}, res) } @@ -106,7 +108,7 @@ func TestNotificationClient_NotificationsByTimeRange(t *testing.T) { ts := newTestServer(http.MethodGet, urlPath, responses.MultiNotificationsResponse{}) defer ts.Close() client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) - res, err := client.NotificationsByTimeRange(context.Background(), start, end, 0, 10) + res, err := client.NotificationsByTimeRange(context.Background(), start, end, 0, 10, "") require.NoError(t, err) require.IsType(t, responses.MultiNotificationsResponse{}, res) } @@ -152,3 +154,34 @@ func TestNotificationClient_DeleteProcessedNotificationsByAge(t *testing.T) { require.NoError(t, err) require.IsType(t, dtoCommon.BaseResponse{}, res) } + +func TestNotificationClient_NotificationsByQueryConditions(t *testing.T) { + ts := newTestServer(http.MethodGet, common.ApiNotificationRoute, responses.MultiNotificationsResponse{}) + defer ts.Close() + client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) + res, err := client.NotificationsByQueryConditions(context.Background(), 0, 10, "", requests.GetNotificationRequest{}) + require.NoError(t, err) + require.IsType(t, responses.MultiNotificationsResponse{}, res) +} + +func TestNotificationClient_DeleteNotificationByIds(t *testing.T) { + ids := []string{ExampleUUID} + path := utils.EscapeAndJoinPath(common.ApiNotificationRoute, common.Ids, strings.Join(ids, common.CommaSeparator)) + ts := newTestServer(http.MethodDelete, path, dtoCommon.BaseResponse{}) + defer ts.Close() + client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) + res, err := client.DeleteNotificationByIds(context.Background(), ids) + require.NoError(t, err) + require.IsType(t, dtoCommon.BaseResponse{}, res) +} + +func TestNotificationClient_UpdateNotificationAckStatusByIds(t *testing.T) { + ids := []string{ExampleUUID} + path := utils.EscapeAndJoinPath(common.ApiNotificationRoute, common.Acknowledge, common.Ids, strings.Join(ids, common.CommaSeparator)) + ts := newTestServer(http.MethodPut, path, dtoCommon.BaseResponse{}) + defer ts.Close() + client := NewNotificationClient(ts.URL, NewNullAuthenticationInjector(), false) + res, err := client.UpdateNotificationAckStatusByIds(context.Background(), true, ids) + require.NoError(t, err) + require.IsType(t, dtoCommon.BaseResponse{}, res) +} diff --git a/clients/interfaces/mocks/NotificationClient.go b/clients/interfaces/mocks/NotificationClient.go index 378eb003..ab964670 100644 --- a/clients/interfaces/mocks/NotificationClient.go +++ b/clients/interfaces/mocks/NotificationClient.go @@ -111,6 +111,36 @@ func (_m *NotificationClient) DeleteNotificationById(ctx context.Context, id str return r0, r1 } +// DeleteNotificationByIds provides a mock function with given fields: ctx, ids +func (_m *NotificationClient) DeleteNotificationByIds(ctx context.Context, ids []string) (common.BaseResponse, errors.EdgeX) { + ret := _m.Called(ctx, ids) + + if len(ret) == 0 { + panic("no return value specified for DeleteNotificationByIds") + } + + var r0 common.BaseResponse + var r1 errors.EdgeX + if rf, ok := ret.Get(0).(func(context.Context, []string) (common.BaseResponse, errors.EdgeX)); ok { + return rf(ctx, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, []string) common.BaseResponse); ok { + r0 = rf(ctx, ids) + } else { + r0 = ret.Get(0).(common.BaseResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, []string) errors.EdgeX); ok { + r1 = rf(ctx, ids) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.EdgeX) + } + } + + return r0, r1 +} + // DeleteProcessedNotificationsByAge provides a mock function with given fields: ctx, age func (_m *NotificationClient) DeleteProcessedNotificationsByAge(ctx context.Context, age int) (common.BaseResponse, errors.EdgeX) { ret := _m.Called(ctx, age) @@ -171,9 +201,9 @@ func (_m *NotificationClient) NotificationById(ctx context.Context, id string) ( return r0, r1 } -// NotificationsByCategory provides a mock function with given fields: ctx, category, offset, limit -func (_m *NotificationClient) NotificationsByCategory(ctx context.Context, category string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) { - ret := _m.Called(ctx, category, offset, limit) +// NotificationsByCategory provides a mock function with given fields: ctx, category, offset, limit, ack +func (_m *NotificationClient) NotificationsByCategory(ctx context.Context, category string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, category, offset, limit, ack) if len(ret) == 0 { panic("no return value specified for NotificationsByCategory") @@ -181,17 +211,17 @@ func (_m *NotificationClient) NotificationsByCategory(ctx context.Context, categ var r0 responses.MultiNotificationsResponse var r1 errors.EdgeX - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { - return rf(ctx, category, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, category, offset, limit, ack) } - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) responses.MultiNotificationsResponse); ok { - r0 = rf(ctx, category, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, category, offset, limit, ack) } else { r0 = ret.Get(0).(responses.MultiNotificationsResponse) } - if rf, ok := ret.Get(1).(func(context.Context, string, int, int) errors.EdgeX); ok { - r1 = rf(ctx, category, offset, limit) + if rf, ok := ret.Get(1).(func(context.Context, string, int, int, string) errors.EdgeX); ok { + r1 = rf(ctx, category, offset, limit, ack) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.EdgeX) @@ -201,9 +231,9 @@ func (_m *NotificationClient) NotificationsByCategory(ctx context.Context, categ return r0, r1 } -// NotificationsByLabel provides a mock function with given fields: ctx, label, offset, limit -func (_m *NotificationClient) NotificationsByLabel(ctx context.Context, label string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) { - ret := _m.Called(ctx, label, offset, limit) +// NotificationsByLabel provides a mock function with given fields: ctx, label, offset, limit, ack +func (_m *NotificationClient) NotificationsByLabel(ctx context.Context, label string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, label, offset, limit, ack) if len(ret) == 0 { panic("no return value specified for NotificationsByLabel") @@ -211,17 +241,47 @@ func (_m *NotificationClient) NotificationsByLabel(ctx context.Context, label st var r0 responses.MultiNotificationsResponse var r1 errors.EdgeX - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { - return rf(ctx, label, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, label, offset, limit, ack) + } + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, label, offset, limit, ack) + } else { + r0 = ret.Get(0).(responses.MultiNotificationsResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, int, int, string) errors.EdgeX); ok { + r1 = rf(ctx, label, offset, limit, ack) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.EdgeX) + } + } + + return r0, r1 +} + +// NotificationsByQueryConditions provides a mock function with given fields: ctx, offset, limit, ack, conditionReq +func (_m *NotificationClient) NotificationsByQueryConditions(ctx context.Context, offset int, limit int, ack string, conditionReq requests.GetNotificationRequest) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, offset, limit, ack, conditionReq) + + if len(ret) == 0 { + panic("no return value specified for NotificationsByQueryConditions") + } + + var r0 responses.MultiNotificationsResponse + var r1 errors.EdgeX + if rf, ok := ret.Get(0).(func(context.Context, int, int, string, requests.GetNotificationRequest) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, offset, limit, ack, conditionReq) } - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) responses.MultiNotificationsResponse); ok { - r0 = rf(ctx, label, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, int, int, string, requests.GetNotificationRequest) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, offset, limit, ack, conditionReq) } else { r0 = ret.Get(0).(responses.MultiNotificationsResponse) } - if rf, ok := ret.Get(1).(func(context.Context, string, int, int) errors.EdgeX); ok { - r1 = rf(ctx, label, offset, limit) + if rf, ok := ret.Get(1).(func(context.Context, int, int, string, requests.GetNotificationRequest) errors.EdgeX); ok { + r1 = rf(ctx, offset, limit, ack, conditionReq) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.EdgeX) @@ -231,9 +291,9 @@ func (_m *NotificationClient) NotificationsByLabel(ctx context.Context, label st return r0, r1 } -// NotificationsByStatus provides a mock function with given fields: ctx, status, offset, limit -func (_m *NotificationClient) NotificationsByStatus(ctx context.Context, status string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) { - ret := _m.Called(ctx, status, offset, limit) +// NotificationsByStatus provides a mock function with given fields: ctx, status, offset, limit, ack +func (_m *NotificationClient) NotificationsByStatus(ctx context.Context, status string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, status, offset, limit, ack) if len(ret) == 0 { panic("no return value specified for NotificationsByStatus") @@ -241,17 +301,17 @@ func (_m *NotificationClient) NotificationsByStatus(ctx context.Context, status var r0 responses.MultiNotificationsResponse var r1 errors.EdgeX - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { - return rf(ctx, status, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, status, offset, limit, ack) } - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) responses.MultiNotificationsResponse); ok { - r0 = rf(ctx, status, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, status, offset, limit, ack) } else { r0 = ret.Get(0).(responses.MultiNotificationsResponse) } - if rf, ok := ret.Get(1).(func(context.Context, string, int, int) errors.EdgeX); ok { - r1 = rf(ctx, status, offset, limit) + if rf, ok := ret.Get(1).(func(context.Context, string, int, int, string) errors.EdgeX); ok { + r1 = rf(ctx, status, offset, limit, ack) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.EdgeX) @@ -261,9 +321,9 @@ func (_m *NotificationClient) NotificationsByStatus(ctx context.Context, status return r0, r1 } -// NotificationsBySubscriptionName provides a mock function with given fields: ctx, subscriptionName, offset, limit -func (_m *NotificationClient) NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) { - ret := _m.Called(ctx, subscriptionName, offset, limit) +// NotificationsBySubscriptionName provides a mock function with given fields: ctx, subscriptionName, offset, limit, ack +func (_m *NotificationClient) NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, subscriptionName, offset, limit, ack) if len(ret) == 0 { panic("no return value specified for NotificationsBySubscriptionName") @@ -271,17 +331,17 @@ func (_m *NotificationClient) NotificationsBySubscriptionName(ctx context.Contex var r0 responses.MultiNotificationsResponse var r1 errors.EdgeX - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { - return rf(ctx, subscriptionName, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, subscriptionName, offset, limit, ack) } - if rf, ok := ret.Get(0).(func(context.Context, string, int, int) responses.MultiNotificationsResponse); ok { - r0 = rf(ctx, subscriptionName, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, string, int, int, string) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, subscriptionName, offset, limit, ack) } else { r0 = ret.Get(0).(responses.MultiNotificationsResponse) } - if rf, ok := ret.Get(1).(func(context.Context, string, int, int) errors.EdgeX); ok { - r1 = rf(ctx, subscriptionName, offset, limit) + if rf, ok := ret.Get(1).(func(context.Context, string, int, int, string) errors.EdgeX); ok { + r1 = rf(ctx, subscriptionName, offset, limit, ack) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.EdgeX) @@ -291,9 +351,9 @@ func (_m *NotificationClient) NotificationsBySubscriptionName(ctx context.Contex return r0, r1 } -// NotificationsByTimeRange provides a mock function with given fields: ctx, start, end, offset, limit -func (_m *NotificationClient) NotificationsByTimeRange(ctx context.Context, start int64, end int64, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) { - ret := _m.Called(ctx, start, end, offset, limit) +// NotificationsByTimeRange provides a mock function with given fields: ctx, start, end, offset, limit, ack +func (_m *NotificationClient) NotificationsByTimeRange(ctx context.Context, start int64, end int64, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) { + ret := _m.Called(ctx, start, end, offset, limit, ack) if len(ret) == 0 { panic("no return value specified for NotificationsByTimeRange") @@ -301,17 +361,17 @@ func (_m *NotificationClient) NotificationsByTimeRange(ctx context.Context, star var r0 responses.MultiNotificationsResponse var r1 errors.EdgeX - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int, int) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { - return rf(ctx, start, end, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int, int, string) (responses.MultiNotificationsResponse, errors.EdgeX)); ok { + return rf(ctx, start, end, offset, limit, ack) } - if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int, int) responses.MultiNotificationsResponse); ok { - r0 = rf(ctx, start, end, offset, limit) + if rf, ok := ret.Get(0).(func(context.Context, int64, int64, int, int, string) responses.MultiNotificationsResponse); ok { + r0 = rf(ctx, start, end, offset, limit, ack) } else { r0 = ret.Get(0).(responses.MultiNotificationsResponse) } - if rf, ok := ret.Get(1).(func(context.Context, int64, int64, int, int) errors.EdgeX); ok { - r1 = rf(ctx, start, end, offset, limit) + if rf, ok := ret.Get(1).(func(context.Context, int64, int64, int, int, string) errors.EdgeX); ok { + r1 = rf(ctx, start, end, offset, limit, ack) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(errors.EdgeX) @@ -353,6 +413,36 @@ func (_m *NotificationClient) SendNotification(ctx context.Context, reqs []reque return r0, r1 } +// UpdateNotificationAckStatusByIds provides a mock function with given fields: ctx, ack, ids +func (_m *NotificationClient) UpdateNotificationAckStatusByIds(ctx context.Context, ack bool, ids []string) (common.BaseResponse, errors.EdgeX) { + ret := _m.Called(ctx, ack, ids) + + if len(ret) == 0 { + panic("no return value specified for UpdateNotificationAckStatusByIds") + } + + var r0 common.BaseResponse + var r1 errors.EdgeX + if rf, ok := ret.Get(0).(func(context.Context, bool, []string) (common.BaseResponse, errors.EdgeX)); ok { + return rf(ctx, ack, ids) + } + if rf, ok := ret.Get(0).(func(context.Context, bool, []string) common.BaseResponse); ok { + r0 = rf(ctx, ack, ids) + } else { + r0 = ret.Get(0).(common.BaseResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, bool, []string) errors.EdgeX); ok { + r1 = rf(ctx, ack, ids) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(errors.EdgeX) + } + } + + return r0, r1 +} + // NewNotificationClient creates a new instance of NotificationClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewNotificationClient(t interface { diff --git a/clients/interfaces/notification.go b/clients/interfaces/notification.go index bd5557a9..89a2f8ac 100644 --- a/clients/interfaces/notification.go +++ b/clients/interfaces/notification.go @@ -22,16 +22,16 @@ type NotificationClient interface { NotificationById(ctx context.Context, id string) (responses.NotificationResponse, errors.EdgeX) // DeleteNotificationById deletes a notification by id. DeleteNotificationById(ctx context.Context, id string) (common.BaseResponse, errors.EdgeX) - // NotificationsByCategory queries notifications with category, offset and limit - NotificationsByCategory(ctx context.Context, category string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) - // NotificationsByLabel queries notifications with label, offset and limit - NotificationsByLabel(ctx context.Context, label string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) - // NotificationsByStatus queries notifications with status, offset and limit - NotificationsByStatus(ctx context.Context, status string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) - // NotificationsByTimeRange query notifications with time range, offset and limit - NotificationsByTimeRange(ctx context.Context, start, end int64, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) - // NotificationsBySubscriptionName query notifications with subscriptionName, offset and limit - NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int) (responses.MultiNotificationsResponse, errors.EdgeX) + // NotificationsByCategory queries notifications with category, offset, ack and limit + NotificationsByCategory(ctx context.Context, category string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) + // NotificationsByLabel queries notifications with label, offset, ack and limit + NotificationsByLabel(ctx context.Context, label string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) + // NotificationsByStatus queries notifications with status, offset, ack and limit + NotificationsByStatus(ctx context.Context, status string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) + // NotificationsByTimeRange query notifications with time range, offset, ack and limit + NotificationsByTimeRange(ctx context.Context, start, end int64, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) + // NotificationsBySubscriptionName query notifications with subscriptionName, offset, ack and limit + NotificationsBySubscriptionName(ctx context.Context, subscriptionName string, offset int, limit int, ack string) (responses.MultiNotificationsResponse, errors.EdgeX) // CleanupNotificationsByAge removes notifications that are older than age. And the corresponding transmissions will also be deleted. // Age is supposed in milliseconds since modified timestamp CleanupNotificationsByAge(ctx context.Context, age int) (common.BaseResponse, errors.EdgeX) @@ -41,4 +41,10 @@ type NotificationClient interface { // Age is supposed in milliseconds since modified timestamp // Please notice that this API is only for processed notifications (status = PROCESSED). If the deletion purpose includes each kind of notifications, please refer to cleanup API. DeleteProcessedNotificationsByAge(ctx context.Context, age int) (common.BaseResponse, errors.EdgeX) + // NotificationsByQueryConditions queries notifications with offset, limit, acknowledgement status, category and time range + NotificationsByQueryConditions(ctx context.Context, offset, limit int, ack string, conditionReq requests.GetNotificationRequest) (responses.MultiNotificationsResponse, errors.EdgeX) + // DeleteNotificationByIds deletes notifications by ids + DeleteNotificationByIds(ctx context.Context, ids []string) (common.BaseResponse, errors.EdgeX) + // UpdateNotificationAckStatusByIds updates existing notification's acknowledgement status + UpdateNotificationAckStatusByIds(ctx context.Context, ack bool, ids []string) (common.BaseResponse, errors.EdgeX) } diff --git a/clients/logger/logger.go b/clients/logger/logger.go index 78bcd7bb..6b894f89 100644 --- a/clients/logger/logger.go +++ b/clients/logger/logger.go @@ -83,7 +83,7 @@ func NewClient(owningServiceName string, logLevel string) LoggingClient { lc.rootLogger = log.WithPrefix( lc.rootLogger, "ts", - log.DefaultTimestampUTC, + log.DefaultTimestamp, "app", owningServiceName, "source", diff --git a/common/constants.go b/common/constants.go index 420963bf..bb2b9f33 100644 --- a/common/constants.go +++ b/common/constants.go @@ -150,6 +150,7 @@ const ( const ( All = "all" Id = "id" + Ids = "ids" Created = "created" Modified = "modified" Pushed = "pushed" @@ -197,6 +198,9 @@ const ( Job = "job" Trigger = "trigger" Latest = "latest" + Ack = "ack" + Acknowledge = "acknowledge" + Unacknowledge = "unacknowledge" Offset = "offset" //query string to specify the number of items to skip before starting to collect the result set. Limit = "limit" //query string to specify the numbers of items to return @@ -314,7 +318,6 @@ const ( CoreKeeperServiceKey = "core-keeper" SupportLoggingServiceKey = "support-logging" SupportNotificationsServiceKey = "support-notifications" - SystemManagementAgentServiceKey = "sys-mgmt-agent" SupportSchedulerServiceKey = "support-scheduler" SecuritySecretStoreSetupServiceKey = "security-secretstore-setup" SecurityProxyAuthServiceKey = "security-proxy-auth" diff --git a/common/utils.go b/common/utils.go index a3a27529..45511cd3 100644 --- a/common/utils.go +++ b/common/utils.go @@ -84,3 +84,28 @@ func (b *pathBuilder) SetNameFieldPath(namePath string) *pathBuilder { func (b *pathBuilder) BuildPath() string { return strings.TrimSuffix(b.sb.String(), "/") } + +// StrValueFromProperties retrieves the string value associated with the given key from the provided properties map. +func StrValueFromProperties(key string, properties map[string]any) (val string) { + if propVal, ok := properties[key]; ok { + val = fmt.Sprintf("%v", propVal) + } + return val +} + +// StrSliceFromProperties retrieves a slice of strings associated with the given key from the provided properties map. +func StrSliceFromProperties(key string, properties map[string]any) []string { + if propVal, ok := properties[key]; ok { + switch slice := propVal.(type) { + case []string: + return slice + case []any: + strSlice := make([]string, len(slice)) + for i, v := range slice { + strSlice[i] = fmt.Sprintf("%v", v) + } + return strSlice + } + } + return []string{} +} diff --git a/common/validator.go b/common/validator.go index dbe78400..82ef4a4f 100644 --- a/common/validator.go +++ b/common/validator.go @@ -104,7 +104,7 @@ func getErrorMessage(e validator.FieldError) string { case "gt": msg = fmt.Sprintf("%s field should greater than %s", fieldName, fieldValue) case dtoDurationTag: - msg = fmt.Sprintf("%s field should follows the ISO 8601 Durations format. Eg,100ms, 24h", fieldName) + msg = fmt.Sprintf("%s field should follows the ISO 8601 Durations format, e.g.,100ms, 24h, or be greater than or equal to the minimum value %s ", fieldName, fieldValue) case dtoUuidTag: msg = fmt.Sprintf("%s field needs a uuid", fieldName) case dtoNoneEmptyStringTag: @@ -124,9 +124,42 @@ func getErrorMessage(e validator.FieldError) string { } // ValidateDuration validate field which should follow the ISO 8601 Durations format +// the min/max of the Duration can be set via the tag params +// ex. edgex-dto-duration=10ms0x2C24h - 10ms represents the minimum Duration and 24h represents the maximum Duration +// 0x2c is the UTF-8 hex encoding of comma (,) as the min/max value separator func ValidateDuration(fl validator.FieldLevel) bool { - _, err := time.ParseDuration(fl.Field().String()) - return err == nil + duration, err := time.ParseDuration(fl.Field().String()) + if err != nil { + return false + } + + // if min/max are defined from tag param, check if the duration value is in the duration range + param := fl.Param() + var min, max time.Duration + if param != "" { + params := strings.Split(param, CommaSeparator) + if len(params) > 0 { + min, err = time.ParseDuration(params[0]) + if err != nil { + return false + } + if duration < min { + // the duration value is smaller than the min + return false + } + if len(params) > 1 { + max, err = time.ParseDuration(params[1]) + if err != nil { + return false + } + if duration > max { + // the duration value is larger than the max + return false + } + } + } + } + return true } // ValidateDtoUuid used to check the UpdateDTO uuid pointer value diff --git a/dtos/autoevent.go b/dtos/autoevent.go index 85452c40..ca3dd090 100644 --- a/dtos/autoevent.go +++ b/dtos/autoevent.go @@ -10,7 +10,7 @@ import ( ) type AutoEvent struct { - Interval string `json:"interval" yaml:"interval" validate:"required,edgex-dto-duration"` + Interval string `json:"interval" yaml:"interval" validate:"required,edgex-dto-duration=1ms"` // min/max can be defined as params, ex. edgex-dto-duration=10ms0x2C24h OnChange bool `json:"onChange" yaml:"onChange"` OnChangeThreshold float64 `json:"onChangeThreshold" yaml:"onChangeThreshold" validate:"gte=0"` SourceName string `json:"sourceName" yaml:"sourceName" validate:"required"` diff --git a/dtos/const_test.go b/dtos/const_test.go index da6284fb..0859cdbe 100644 --- a/dtos/const_test.go +++ b/dtos/const_test.go @@ -24,4 +24,7 @@ const ( TestDeviceResourceName = "TestDeviceResourceName" TestDeviceCommandName = "TestDeviceCommand" + + TestTag1 = "TestTag1" + TestTag2 = "TestTag2" ) diff --git a/dtos/deviceprofile.go b/dtos/deviceprofile.go index a92a93d5..10182ef8 100644 --- a/dtos/deviceprofile.go +++ b/dtos/deviceprofile.go @@ -19,6 +19,7 @@ type DeviceProfile struct { DeviceProfileBasicInfo `json:",inline" yaml:",inline"` DeviceResources []DeviceResource `json:"deviceResources" yaml:"deviceResources" validate:"dive"` DeviceCommands []DeviceCommand `json:"deviceCommands" yaml:"deviceCommands" validate:"dive"` + ApiVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` } // Validate satisfies the Validator interface @@ -39,6 +40,7 @@ func (dp *DeviceProfile) UnmarshalYAML(unmarshal func(interface{}) error) error DeviceProfileBasicInfo `yaml:",inline"` DeviceResources []DeviceResource `yaml:"deviceResources"` DeviceCommands []DeviceCommand `yaml:"deviceCommands"` + ApiVersion string `yaml:"apiVersion"` } if err := unmarshal(&alias); err != nil { return edgexErrors.NewCommonEdgeX(edgexErrors.KindContractInvalid, "failed to unmarshal request body as YAML.", err) @@ -72,11 +74,15 @@ func ToDeviceProfileModel(deviceProfileDTO DeviceProfile) models.DeviceProfile { Labels: deviceProfileDTO.Labels, DeviceResources: ToDeviceResourceModels(deviceProfileDTO.DeviceResources), DeviceCommands: ToDeviceCommandModels(deviceProfileDTO.DeviceCommands), + ApiVersion: deviceProfileDTO.ApiVersion, } } // FromDeviceProfileModelToDTO transforms the DeviceProfile Model to the DeviceProfile DTO func FromDeviceProfileModelToDTO(deviceProfile models.DeviceProfile) DeviceProfile { + if deviceProfile.ApiVersion == "" { + deviceProfile.ApiVersion = common.ApiVersion + } return DeviceProfile{ DeviceProfileBasicInfo: DeviceProfileBasicInfo{ DBTimestamp: DBTimestamp(deviceProfile.DBTimestamp), @@ -89,6 +95,7 @@ func FromDeviceProfileModelToDTO(deviceProfile models.DeviceProfile) DeviceProfi }, DeviceResources: FromDeviceResourceModelsToDTOs(deviceProfile.DeviceResources), DeviceCommands: FromDeviceCommandModelsToDTOs(deviceProfile.DeviceCommands), + ApiVersion: deviceProfile.ApiVersion, } } diff --git a/dtos/deviceprofile_test.go b/dtos/deviceprofile_test.go index 1468b8d0..148020a1 100644 --- a/dtos/deviceprofile_test.go +++ b/dtos/deviceprofile_test.go @@ -26,6 +26,7 @@ var testTags = map[string]any{"TestTagsKey": "TestTagsValue"} var testOptional = map[string]any{"isVirtual": false} var testDeviceProfile = models.DeviceProfile{ + ApiVersion: common.ApiVersion, Name: TestDeviceProfileName, Manufacturer: TestManufacturer, Description: TestDescription, @@ -55,6 +56,7 @@ var testDeviceProfile = models.DeviceProfile{ func profileData() DeviceProfile { return DeviceProfile{ + ApiVersion: common.ApiVersion, DeviceProfileBasicInfo: DeviceProfileBasicInfo{ Name: TestDeviceProfileName, Manufacturer: TestManufacturer, diff --git a/dtos/deviceprofilebasicinfo.go b/dtos/deviceprofilebasicinfo.go index 2930f19b..256804f4 100644 --- a/dtos/deviceprofilebasicinfo.go +++ b/dtos/deviceprofilebasicinfo.go @@ -6,13 +6,13 @@ package dtos type DeviceProfileBasicInfo struct { - DBTimestamp `json:",inline"` - Id string `json:"id" validate:"omitempty,uuid"` + DBTimestamp `json:",inline" yaml:"dbTimestamp,omitempty"` + Id string `json:"id,omitempty" validate:"omitempty,uuid" yaml:"id,omitempty"` Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"` - Manufacturer string `json:"manufacturer" yaml:"manufacturer"` - Description string `json:"description" yaml:"description"` - Model string `json:"model" yaml:"model"` - Labels []string `json:"labels" yaml:"labels,flow"` + Manufacturer string `json:"manufacturer,omitempty" yaml:"manufacturer,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Model string `json:"model,omitempty" yaml:"model,omitempty"` + Labels []string `json:"labels,omitempty" yaml:"labels,flow,omitempty"` } type UpdateDeviceProfileBasicInfo struct { diff --git a/dtos/deviceresource.go b/dtos/deviceresource.go index 2b9d58ea..7c741ccf 100644 --- a/dtos/deviceresource.go +++ b/dtos/deviceresource.go @@ -1,21 +1,35 @@ // -// Copyright (C) 2020-2023 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 package dtos -import "github.com/edgexfoundry/go-mod-core-contracts/v4/models" +import ( + "github.com/edgexfoundry/go-mod-core-contracts/v4/common" + "github.com/edgexfoundry/go-mod-core-contracts/v4/errors" + "github.com/edgexfoundry/go-mod-core-contracts/v4/models" +) type DeviceResource struct { - Description string `json:"description" yaml:"description"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` Name string `json:"name" yaml:"name" validate:"required,edgex-dto-none-empty-string"` IsHidden bool `json:"isHidden" yaml:"isHidden"` Properties ResourceProperties `json:"properties" yaml:"properties"` - Attributes map[string]interface{} `json:"attributes" yaml:"attributes"` + Attributes map[string]interface{} `json:"attributes,omitempty" yaml:"attributes,omitempty"` Tags map[string]any `json:"tags,omitempty" yaml:"tags,omitempty"` } +// Validate satisfies the Validator interface +func (dr *DeviceResource) Validate() error { + err := common.Validate(dr) + if err != nil { + return errors.NewCommonEdgeX(errors.KindContractInvalid, "invalid DeviceResource.", err) + } + + return nil +} + type UpdateDeviceResource struct { Description *string `json:"description"` Name *string `json:"name" validate:"required,edgex-dto-none-empty-string"` diff --git a/dtos/deviceservice.go b/dtos/deviceservice.go index c9cdcae1..d2a2f4b3 100644 --- a/dtos/deviceservice.go +++ b/dtos/deviceservice.go @@ -11,21 +11,23 @@ import ( type DeviceService struct { DBTimestamp `json:",inline"` - Id string `json:"id,omitempty" validate:"omitempty,uuid"` - Name string `json:"name" validate:"required,edgex-dto-none-empty-string"` - Description string `json:"description,omitempty"` - Labels []string `json:"labels,omitempty"` - BaseAddress string `json:"baseAddress" validate:"required,uri"` - AdminState string `json:"adminState" validate:"oneof='LOCKED' 'UNLOCKED'"` + Id string `json:"id,omitempty" validate:"omitempty,uuid"` + Name string `json:"name" validate:"required,edgex-dto-none-empty-string"` + Description string `json:"description,omitempty"` + Labels []string `json:"labels,omitempty"` + BaseAddress string `json:"baseAddress" validate:"required,uri"` + AdminState string `json:"adminState" validate:"oneof='LOCKED' 'UNLOCKED'"` + Properties map[string]any `json:"properties,omitempty" yaml:"properties,omitempty"` } type UpdateDeviceService struct { - Id *string `json:"id" validate:"required_without=Name,edgex-dto-uuid"` - Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string"` - Description *string `json:"description"` - BaseAddress *string `json:"baseAddress" validate:"omitempty,uri"` - Labels []string `json:"labels"` - AdminState *string `json:"adminState" validate:"omitempty,oneof='LOCKED' 'UNLOCKED'"` + Id *string `json:"id" validate:"required_without=Name,edgex-dto-uuid"` + Name *string `json:"name" validate:"required_without=Id,edgex-dto-none-empty-string"` + Description *string `json:"description"` + BaseAddress *string `json:"baseAddress" validate:"omitempty,uri"` + Labels []string `json:"labels"` + AdminState *string `json:"adminState" validate:"omitempty,oneof='LOCKED' 'UNLOCKED'"` + Properties map[string]any `json:"properties"` } // ToDeviceServiceModel transforms the DeviceService DTO to the DeviceService Model @@ -37,6 +39,7 @@ func ToDeviceServiceModel(dto DeviceService) models.DeviceService { ds.BaseAddress = dto.BaseAddress ds.Labels = dto.Labels ds.AdminState = models.AdminState(dto.AdminState) + ds.Properties = dto.Properties return ds } @@ -50,6 +53,7 @@ func FromDeviceServiceModelToDTO(ds models.DeviceService) DeviceService { dto.BaseAddress = ds.BaseAddress dto.Labels = ds.Labels dto.AdminState = string(ds.AdminState) + dto.Properties = ds.Properties return dto } @@ -63,6 +67,7 @@ func FromDeviceServiceModelToUpdateDTO(ds models.DeviceService) UpdateDeviceServ Labels: ds.Labels, BaseAddress: &ds.BaseAddress, AdminState: &adminState, + Properties: ds.Properties, } return dto } diff --git a/dtos/deviceservice_test.go b/dtos/deviceservice_test.go index f061989d..427e3026 100644 --- a/dtos/deviceservice_test.go +++ b/dtos/deviceservice_test.go @@ -21,4 +21,5 @@ func TestFromDeviceServiceModelToUpdateDTO(t *testing.T) { assert.Equal(t, model.Labels, dto.Labels) assert.Equal(t, model.Id, *dto.BaseAddress) assert.EqualValues(t, model.Id, *dto.AdminState) + assert.Equal(t, model.Properties, dto.Properties) } diff --git a/dtos/event.go b/dtos/event.go index eacc85c1..58b9cf9e 100644 --- a/dtos/event.go +++ b/dtos/event.go @@ -20,7 +20,7 @@ type Event struct { Id string `json:"id" validate:"required,uuid"` DeviceName string `json:"deviceName" validate:"required,edgex-dto-none-empty-string"` ProfileName string `json:"profileName" validate:"required,edgex-dto-none-empty-string"` - SourceName string `json:"sourceName" validate:"required"` + SourceName string `json:"sourceName" validate:"required,edgex-dto-none-empty-string"` Origin int64 `json:"origin" validate:"required"` Readings []BaseReading `json:"readings" validate:"gt=0,dive,required"` Tags Tags `json:"tags,omitempty"` diff --git a/dtos/event_test.go b/dtos/event_test.go index 9a27de35..a8700996 100644 --- a/dtos/event_test.go +++ b/dtos/event_test.go @@ -79,6 +79,10 @@ func TestFromEventModelToDTO(t *testing.T) { } func TestEvent_ToXML(t *testing.T) { + reading := newBaseReading(TestDeviceProfileName, TestDeviceName, TestSourceName, TestValueType) + reading.Tags = map[string]any{"1": TestTag1, "2": TestTag2} + reading.Origin = TestTimestamp + reading.Id = TestUUID var expectedDTO = Event{ Versionable: dtoCommon.Versionable{ApiVersion: common.ApiVersion}, Id: TestUUID, @@ -91,10 +95,19 @@ func TestEvent_ToXML(t *testing.T) { "Latitude": "29.630771", "Longitude": "-95.377603", }, + Readings: []BaseReading{ + reading, + }, } // Since the order in map is random we have to verify the individual items exists without depending on order contains := []string{ - "v37a1707f0-166f-4c4b-bc9d-1d54c74e0137TestDeviceTestDeviceProfileNameTestSourceName1594963842", + "v37a1707f0-166f-4c4b-bc9d-1d54c74e0137TestDeviceTestDeviceProfileNameTestSourceName1594963842", + "7a1707f0-166f-4c4b-bc9d-1d54c74e01371594963842TestDeviceTestSourceNameTestDeviceProfileName", + "Int8", + "<1>TestTag1", + "<2>TestTag2", + "", + "", "Houston-0001", "29.630771", "-95.377603", diff --git a/dtos/notification.go b/dtos/notification.go index 4f19f67d..700bb48a 100644 --- a/dtos/notification.go +++ b/dtos/notification.go @@ -12,16 +12,17 @@ import ( ) type Notification struct { - DBTimestamp `json:",inline"` - Id string `json:"id,omitempty" validate:"omitempty,uuid"` - Category string `json:"category,omitempty" validate:"required_without=Labels,omitempty,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` - Labels []string `json:"labels,omitempty" validate:"required_without=Category,omitempty,gt=0,dive,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` - Content string `json:"content" validate:"required,edgex-dto-none-empty-string"` - ContentType string `json:"contentType,omitempty"` - Description string `json:"description,omitempty"` - Sender string `json:"sender" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` - Severity string `json:"severity" validate:"required,oneof='MINOR' 'NORMAL' 'CRITICAL'"` - Status string `json:"status,omitempty" validate:"omitempty,oneof='NEW' 'PROCESSED' 'ESCALATED'"` + DBTimestamp `json:",inline"` + Id string `json:"id,omitempty" validate:"omitempty,uuid"` + Category string `json:"category,omitempty" validate:"required_without=Labels,omitempty,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` + Labels []string `json:"labels,omitempty" validate:"required_without=Category,omitempty,gt=0,dive,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` + Content string `json:"content" validate:"required,edgex-dto-none-empty-string"` + ContentType string `json:"contentType,omitempty"` + Description string `json:"description,omitempty"` + Sender string `json:"sender" validate:"required,edgex-dto-none-empty-string,edgex-dto-rfc3986-unreserved-chars"` + Severity string `json:"severity" validate:"required,oneof='MINOR' 'NORMAL' 'CRITICAL'"` + Status string `json:"status,omitempty" validate:"omitempty,oneof='NEW' 'PROCESSED' 'ESCALATED'"` + Acknowledged bool `json:"acknowledged"` } // NewNotification creates and returns a Notification DTO @@ -49,6 +50,7 @@ func ToNotificationModel(n Notification) models.Notification { m.Sender = n.Sender m.Severity = models.NotificationSeverity(n.Severity) m.Status = models.NotificationStatus(n.Status) + m.Acknowledged = n.Acknowledged return m } @@ -64,16 +66,17 @@ func ToNotificationModels(notifications []Notification) []models.Notification { // FromNotificationModelToDTO transforms the Notification Model to the Notification DTO func FromNotificationModelToDTO(n models.Notification) Notification { return Notification{ - DBTimestamp: DBTimestamp(n.DBTimestamp), - Id: n.Id, - Category: string(n.Category), - Labels: n.Labels, - Content: n.Content, - ContentType: n.ContentType, - Description: n.Description, - Sender: n.Sender, - Severity: string(n.Severity), - Status: string(n.Status), + DBTimestamp: DBTimestamp(n.DBTimestamp), + Id: n.Id, + Category: string(n.Category), + Labels: n.Labels, + Content: n.Content, + ContentType: n.ContentType, + Description: n.Description, + Sender: n.Sender, + Severity: string(n.Severity), + Status: string(n.Status), + Acknowledged: n.Acknowledged, } } diff --git a/dtos/notification_test.go b/dtos/notification_test.go index 642a4563..4fd72083 100644 --- a/dtos/notification_test.go +++ b/dtos/notification_test.go @@ -28,4 +28,5 @@ func TestNewNotification(t *testing.T) { assert.Empty(t, actual.Status) assert.Zero(t, actual.Created) assert.Zero(t, actual.Modified) + assert.False(t, actual.Acknowledged) } diff --git a/dtos/reading.go b/dtos/reading.go index 2946e956..32df7a21 100644 --- a/dtos/reading.go +++ b/dtos/reading.go @@ -25,7 +25,7 @@ type BaseReading struct { Id string `json:"id,omitempty"` Origin int64 `json:"origin" validate:"required"` DeviceName string `json:"deviceName" validate:"required,edgex-dto-none-empty-string"` - ResourceName string `json:"resourceName" validate:"required"` + ResourceName string `json:"resourceName" validate:"required,edgex-dto-none-empty-string"` ProfileName string `json:"profileName" validate:"required,edgex-dto-none-empty-string"` ValueType string `json:"valueType" validate:"required,edgex-dto-value-type"` Units string `json:"units,omitempty"` diff --git a/dtos/reading_test.go b/dtos/reading_test.go index bb0dcb46..5684a491 100644 --- a/dtos/reading_test.go +++ b/dtos/reading_test.go @@ -608,6 +608,35 @@ func TestUnmarshalObjectValueError(t *testing.T) { } } +func TestNewObjectArrayReading(t *testing.T) { + expectedDeviceName := TestDeviceName + expectedProfileName := TestDeviceProfileName + expectedResourceName := TestDeviceResourceName + expectedValueType := common.ValueTypeObjectArray + expectedValue := []map[string]interface{}{ + { + "Attr1": "yyz", + "Attr2": -45, + "Attr3": []interface{}{255, 1, 0}, + }, + { + "Attr1": "abc", + "Attr2": -50, + "Attr3": []interface{}{255, 1, 0}, + }, + } + + actual := NewObjectReadingWithArray(expectedProfileName, expectedDeviceName, expectedResourceName, expectedValue) + + assert.NotEmpty(t, actual.Id) + assert.Equal(t, expectedProfileName, actual.ProfileName) + assert.Equal(t, expectedDeviceName, actual.DeviceName) + assert.Equal(t, expectedResourceName, actual.ResourceName) + assert.Equal(t, expectedValueType, actual.ValueType) + assert.Equal(t, expectedValue, actual.ObjectValue) + assert.NotZero(t, actual.Origin) +} + func TestMarshalObjectReading(t *testing.T) { tests := []struct { name string diff --git a/dtos/requests/const_test.go b/dtos/requests/const_test.go index 2f0be1b7..3e13837a 100644 --- a/dtos/requests/const_test.go +++ b/dtos/requests/const_test.go @@ -21,7 +21,6 @@ const ( TestSourceName = "TestSourceName" TestDeviceResourceName = "TestDeviceResourceName" - TestTag = "TestTag" TestDeviceCommandName = "TestDeviceCommand" diff --git a/dtos/requests/device_test.go b/dtos/requests/device_test.go index f5083e4d..85ffbc4c 100644 --- a/dtos/requests/device_test.go +++ b/dtos/requests/device_test.go @@ -34,7 +34,13 @@ var testProtocols = map[string]dtos.ProtocolProperties{ "UnitID": "1", }, } + +var testProperties = map[string]any{ + "LastScan": float64(1702275547), +} + var testParent = "ParentDevice" + var testAddDevice = AddDeviceRequest{ BaseRequest: dtoCommon.BaseRequest{ RequestId: ExampleUUID, @@ -48,6 +54,7 @@ var testAddDevice = AddDeviceRequest{ OperatingState: models.Up, Labels: testDeviceLabels, Location: testDeviceLocation, + Tags: testTags, AutoEvents: testAutoEvents, Protocols: testProtocols, Parent: testParent, @@ -82,6 +89,8 @@ func mockUpdateDevice() dtos.UpdateDevice { d.Location = testDeviceLocation d.AutoEvents = testAutoEvents d.Protocols = testProtocols + d.Tags = testTags + d.Properties = testProperties d.Parent = &testParent return d } @@ -235,6 +244,7 @@ func Test_AddDeviceReqToDeviceModels(t *testing.T) { OperatingState: models.Up, Labels: testDeviceLabels, Location: testDeviceLocation, + Tags: testTags, AutoEvents: []models.AutoEvent{ {SourceName: "TestDevice", Interval: "300ms", OnChange: true, OnChangeThreshold: 0.01}, }, @@ -436,8 +446,10 @@ func TestReplaceDeviceModelFieldsWithDTO(t *testing.T) { assert.Equal(t, TestDeviceProfileName, device.ProfileName) assert.Equal(t, testLabels, device.Labels) assert.Equal(t, testDeviceLocation, device.Location) + assert.Equal(t, testTags, device.Tags) assert.Equal(t, dtos.ToAutoEventModels(testAutoEvents), device.AutoEvents) assert.Equal(t, dtos.ToProtocolModels(testProtocols), device.Protocols) + assert.Equal(t, testProperties, device.Properties) } func TestNewAddDeviceRequest(t *testing.T) { diff --git a/dtos/requests/deviceprofile_test.go b/dtos/requests/deviceprofile_test.go index ca0b4bbc..15ca97bf 100644 --- a/dtos/requests/deviceprofile_test.go +++ b/dtos/requests/deviceprofile_test.go @@ -52,6 +52,7 @@ func profileData() DeviceProfileRequest { Versionable: dtoCommon.NewVersionable(), }, Profile: dtos.DeviceProfile{ + ApiVersion: common.ApiVersion, DeviceProfileBasicInfo: dtos.DeviceProfileBasicInfo{ Name: TestDeviceProfileName, Manufacturer: TestManufacturer, @@ -66,6 +67,7 @@ func profileData() DeviceProfileRequest { } var expectedDeviceProfile = models.DeviceProfile{ + ApiVersion: common.ApiVersion, Name: TestDeviceProfileName, Manufacturer: TestManufacturer, Description: TestDescription, diff --git a/dtos/requests/deviceservice.go b/dtos/requests/deviceservice.go index 418de2e2..77df81f1 100644 --- a/dtos/requests/deviceservice.go +++ b/dtos/requests/deviceservice.go @@ -100,6 +100,9 @@ func ReplaceDeviceServiceModelFieldsWithDTO(ds *models.DeviceService, patch dtos if patch.BaseAddress != nil { ds.BaseAddress = *patch.BaseAddress } + if patch.Properties != nil { + ds.Properties = patch.Properties + } } func NewAddDeviceServiceRequest(dto dtos.DeviceService) AddDeviceServiceRequest { diff --git a/dtos/requests/deviceservice_test.go b/dtos/requests/deviceservice_test.go index a98258cc..494f9d0f 100644 --- a/dtos/requests/deviceservice_test.go +++ b/dtos/requests/deviceservice_test.go @@ -301,9 +301,11 @@ func TestUpdateDeviceServiceRequest_UnmarshalJSON_EmptySlice(t *testing.T) { } func TestReplaceDeviceServiceModelFieldsWithDTO(t *testing.T) { + properties := testProperties ds := models.DeviceService{ - Id: "7a1707f0-166f-4c4b-bc9d-1d54c74e0137", - Name: "test device service", + Id: "7a1707f0-166f-4c4b-bc9d-1d54c74e0137", + Name: "test device service", + Properties: properties, } patch := mockDeviceServiceDTO() @@ -312,6 +314,7 @@ func TestReplaceDeviceServiceModelFieldsWithDTO(t *testing.T) { assert.Equal(t, TestBaseAddress, ds.BaseAddress) assert.Equal(t, models.Locked, string(ds.AdminState)) assert.Equal(t, testLabels, ds.Labels) + assert.Equal(t, properties, ds.Properties) } func TestNewAddDeviceServiceRequest(t *testing.T) { diff --git a/dtos/requests/notification.go b/dtos/requests/notification.go index 7cae7c9a..8a4f7c5c 100644 --- a/dtos/requests/notification.go +++ b/dtos/requests/notification.go @@ -61,3 +61,40 @@ func NewAddNotificationRequest(dto dtos.Notification) AddNotificationRequest { Notification: dto, } } + +// GetNotificationRequest defines the Request Content for GET Notification DTO. +type GetNotificationRequest struct { + dtoCommon.BaseRequest `json:",inline"` + QueryCondition NotificationQueryCondition `json:"queryCondition"` +} + +type NotificationQueryCondition struct { + Category []string `json:"category,omitempty"` + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` +} + +// Validate satisfies the Validator interface +func (request GetNotificationRequest) Validate() error { + err := common.Validate(request) + return err +} + +// UnmarshalJSON implements the Unmarshaler interface for the GetNotificationRequest type +func (request *GetNotificationRequest) UnmarshalJSON(b []byte) error { + var alias struct { + dtoCommon.BaseRequest + QueryCondition NotificationQueryCondition + } + if err := json.Unmarshal(b, &alias); err != nil { + return errors.NewCommonEdgeX(errors.KindContractInvalid, "Failed to unmarshal request body as JSON.", err) + } + + *request = GetNotificationRequest(alias) + + // validate GetNotificationRequest DTO + if err := request.Validate(); err != nil { + return err + } + return nil +} diff --git a/dtos/requests/notification_test.go b/dtos/requests/notification_test.go index 54c2b6d4..70fbc6af 100644 --- a/dtos/requests/notification_test.go +++ b/dtos/requests/notification_test.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2021 IOTech Ltd +// Copyright (C) 2021-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -10,6 +10,7 @@ import ( "testing" "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos" + dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common" "github.com/edgexfoundry/go-mod-core-contracts/v4/models" "github.com/stretchr/testify/assert" @@ -131,3 +132,67 @@ func TestAddNotificationReqToNotificationModels(t *testing.T) { resultModels := AddNotificationReqToNotificationModels(requests) assert.Equal(t, expectedNotificationModel, resultModels, "AddNotificationReqToNotificationModels did not result in expected Notification model.") } + +func buildTestGetNotificationRequest() GetNotificationRequest { + return GetNotificationRequest{ + BaseRequest: dtoCommon.NewBaseRequest(), + QueryCondition: NotificationQueryCondition{ + Category: []string{testNotificationCategory}, + Start: 0, + End: 20, + }, + } +} + +func TestGetNotificationRequest_Validate(t *testing.T) { + noReqId := buildTestGetNotificationRequest() + noReqId.RequestId = "" + invalidReqId := buildTestGetNotificationRequest() + invalidReqId.RequestId = "abc" + + noCategory := buildTestGetNotificationRequest() + noCategory.QueryCondition.Category = []string{} + + tests := []struct { + name string + request GetNotificationRequest + expectError bool + }{ + {"valid", buildTestGetNotificationRequest(), false}, + {"valid, no category", noCategory, false}, + {"invalid, request ID is not an UUID", invalidReqId, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.request.Validate() + assert.Equal(t, tt.expectError, err != nil, "Unexpected GetNotificationRequest validation result.", err) + }) + } +} + +func TestGetNotificationRequest_UnmarshalJSON(t *testing.T) { + getNotificationRequest := buildTestGetNotificationRequest() + jsonData, _ := json.Marshal(getNotificationRequest) + tests := []struct { + name string + expected GetNotificationRequest + data []byte + wantErr bool + }{ + {"unmarshal GetNotificationRequest with success", getNotificationRequest, jsonData, false}, + {"unmarshal invalid GetNotificationRequest, empty data", GetNotificationRequest{}, []byte{}, true}, + {"unmarshal invalid GetNotificationRequest, string data", GetNotificationRequest{}, []byte("Invalid GetNotificationRequest"), true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result GetNotificationRequest + err := result.UnmarshalJSON(tt.data) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expected, result, "Unmarshal did not result in expected GetNotificationRequest.") + } + }) + } +} diff --git a/dtos/requests/operation.go b/dtos/requests/operation.go deleted file mode 100644 index 59c86a88..00000000 --- a/dtos/requests/operation.go +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (C) 2021 IOTech Ltd -// -// SPDX-License-Identifier: Apache-2.0 - -package requests - -import ( - "encoding/json" - - "github.com/edgexfoundry/go-mod-core-contracts/v4/common" - dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common" - "github.com/edgexfoundry/go-mod-core-contracts/v4/errors" -) - -// OperationRequest defines the Request Content for SMA POST Operation. -type OperationRequest struct { - dtoCommon.BaseRequest `json:",inline"` - ServiceName string `json:"serviceName" validate:"required"` - Action string `json:"action" validate:"oneof='start' 'stop' 'restart'"` -} - -// Validate satisfies the Validator interface -func (o *OperationRequest) Validate() error { - err := common.Validate(o) - return err -} - -// UnmarshalJSON implements the Unmarshaler interface for the OperationRequest type -func (o *OperationRequest) UnmarshalJSON(b []byte) error { - alias := struct { - dtoCommon.BaseRequest - ServiceName string - Action string - }{} - - if err := json.Unmarshal(b, &alias); err != nil { - return errors.NewCommonEdgeX(errors.KindContractInvalid, "Failed to unmarshal request body as JSON.", err) - } - *o = OperationRequest(alias) - - if err := o.Validate(); err != nil { - return err - } - - return nil -} diff --git a/dtos/requests/operation_test.go b/dtos/requests/operation_test.go deleted file mode 100644 index 3d562dd4..00000000 --- a/dtos/requests/operation_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (C) 2021 IOTech Ltd -// -// SPDX-License-Identifier: Apache-2.0 - -package requests - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common" -) - -var testOperationRequest = OperationRequest{ - BaseRequest: common.BaseRequest{ - RequestId: ExampleUUID, - Versionable: common.NewVersionable(), - }, - ServiceName: TestServiceName, - Action: TestActionName, -} - -func TestOperationRequest_Validate(t *testing.T) { - valid := testOperationRequest - noReqId := testOperationRequest - noReqId.RequestId = "" - invalidReqId := testOperationRequest - invalidReqId.RequestId = "abc" - noServiceName := testOperationRequest - noServiceName.ServiceName = "" - noAction := testOperationRequest - noAction.Action = "" - invalidAction := testOperationRequest - invalidAction.Action = "remove" - - tests := []struct { - name string - request OperationRequest - expectedErr bool - }{ - {"valid", valid, false}, - {"valid - no Request Id", noReqId, false}, - {"invalid - RequestId is not an uuid", invalidReqId, true}, - {"invalid - no ServiceName", noServiceName, true}, - {"invalid - no Action", noAction, true}, - {"invalid - invalid Action", invalidAction, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.request.Validate() - if tt.expectedErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestOperationRequest_UnmarshalJSON(t *testing.T) { - valid := testOperationRequest - resultTestBytes, _ := json.Marshal(testOperationRequest) - type args struct { - data []byte - } - tests := []struct { - name string - request OperationRequest - args args - expectedErr bool - }{ - {"valid", valid, args{resultTestBytes}, false}, - {"invalid - empty data", OperationRequest{}, args{[]byte{}}, true}, - {"invalid - string data", OperationRequest{}, args{[]byte("Invalid OperationRequest")}, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var expected = tt.request - err := tt.request.UnmarshalJSON(tt.args.data) - if tt.expectedErr { - require.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, expected, tt.request, "Unmarshal did not result in expected AddProvisionWatcherRequest.") - } - }) - } -} diff --git a/dtos/requests/schedulejob.go b/dtos/requests/schedulejob.go index ace3fb98..6452ab1c 100644 --- a/dtos/requests/schedulejob.go +++ b/dtos/requests/schedulejob.go @@ -7,12 +7,12 @@ package requests import ( "encoding/json" - "github.com/edgexfoundry/go-mod-core-contracts/v4/models" "github.com/edgexfoundry/go-mod-core-contracts/v4/common" "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos" dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common" "github.com/edgexfoundry/go-mod-core-contracts/v4/errors" + "github.com/edgexfoundry/go-mod-core-contracts/v4/models" ) // AddScheduleJobRequest defines the Request Content for POST ScheduleJob DTO. diff --git a/dtos/resourceoperation.go b/dtos/resourceoperation.go index f6d98a6a..51c7797b 100644 --- a/dtos/resourceoperation.go +++ b/dtos/resourceoperation.go @@ -9,8 +9,8 @@ import "github.com/edgexfoundry/go-mod-core-contracts/v4/models" type ResourceOperation struct { DeviceResource string `json:"deviceResource" yaml:"deviceResource" validate:"required"` // The replacement of Object field - DefaultValue string `json:"defaultValue" yaml:"defaultValue"` - Mappings map[string]string `json:"mappings" yaml:"mappings"` + DefaultValue string `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"` + Mappings map[string]string `json:"mappings,omitempty" yaml:"mappings,omitempty"` } // ToResourceOperationModel transforms the ResourceOperation DTO to the ResourceOperation model diff --git a/dtos/resourceproperties.go b/dtos/resourceproperties.go index 9bfcf4db..539fa618 100644 --- a/dtos/resourceproperties.go +++ b/dtos/resourceproperties.go @@ -12,17 +12,17 @@ import ( type ResourceProperties struct { ValueType string `json:"valueType" yaml:"valueType" validate:"required,edgex-dto-value-type"` ReadWrite string `json:"readWrite" yaml:"readWrite" validate:"required,oneof='R' 'W' 'RW' 'WR'"` - Units string `json:"units,omitempty" yaml:"units"` - Minimum *float64 `json:"minimum,omitempty" yaml:"minimum"` - Maximum *float64 `json:"maximum,omitempty" yaml:"maximum"` - DefaultValue string `json:"defaultValue,omitempty" yaml:"defaultValue"` - Mask *uint64 `json:"mask,omitempty" yaml:"mask"` - Shift *int64 `json:"shift,omitempty" yaml:"shift"` - Scale *float64 `json:"scale,omitempty" yaml:"scale"` - Offset *float64 `json:"offset,omitempty" yaml:"offset"` - Base *float64 `json:"base,omitempty" yaml:"base"` - Assertion string `json:"assertion,omitempty" yaml:"assertion"` - MediaType string `json:"mediaType,omitempty" yaml:"mediaType"` + Units string `json:"units,omitempty" yaml:"units,omitempty"` + Minimum *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Maximum *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"` + DefaultValue string `json:"defaultValue,omitempty" yaml:"defaultValue,omitempty"` + Mask *uint64 `json:"mask,omitempty" yaml:"mask,omitempty"` + Shift *int64 `json:"shift,omitempty" yaml:"shift,omitempty"` + Scale *float64 `json:"scale,omitempty" yaml:"scale,omitempty"` + Offset *float64 `json:"offset,omitempty" yaml:"offset,omitempty"` + Base *float64 `json:"base,omitempty" yaml:"base,omitempty"` + Assertion string `json:"assertion,omitempty" yaml:"assertion,omitempty"` + MediaType string `json:"mediaType,omitempty" yaml:"mediaType,omitempty"` Optional map[string]any `json:"optional,omitempty" yaml:"optional"` } diff --git a/models/deviceprofile.go b/models/deviceprofile.go index 0fd2b758..577fe32a 100644 --- a/models/deviceprofile.go +++ b/models/deviceprofile.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -7,6 +7,7 @@ package models type DeviceProfile struct { DBTimestamp + ApiVersion string Description string Id string Name string diff --git a/models/deviceservice.go b/models/deviceservice.go index a3e6f51c..9349936a 100644 --- a/models/deviceservice.go +++ b/models/deviceservice.go @@ -1,5 +1,5 @@ // -// Copyright (C) 2020-2023 IOTech Ltd +// Copyright (C) 2020-2024 IOTech Ltd // // SPDX-License-Identifier: Apache-2.0 @@ -13,4 +13,5 @@ type DeviceService struct { Labels []string BaseAddress string AdminState AdminState + Properties map[string]any } diff --git a/models/notification.go b/models/notification.go index 89bbcb9e..4635257a 100644 --- a/models/notification.go +++ b/models/notification.go @@ -7,15 +7,16 @@ package models type Notification struct { DBTimestamp - Category string - Content string - ContentType string - Description string - Id string - Labels []string - Sender string - Severity NotificationSeverity - Status NotificationStatus + Category string + Content string + ContentType string + Description string + Id string + Labels []string + Sender string + Severity NotificationSeverity + Status NotificationStatus + Acknowledged bool } // NotificationSeverity indicates the level of severity for the notification.