Skip to content

Commit edea6f0

Browse files
lubo-svklubo-kistlerjkralik
authored
Feature/no link callback (#510)
* Add WithLinkNotFoundCallback for non-discoverable resources When a resource is non-discoverable via any link or collection we can utilize the WithLinkNotFoundCallback option and provide the link by other means. This addition is required when you need to provide thousands of resources and making them discoverable or addiding them to some collection would result in an unusable device due to resources being too big for efectively processing them. --------- Co-authored-by: cah <[email protected]> Co-authored-by: Jozef Kralik <[email protected]>
1 parent df5db5c commit edea6f0

14 files changed

+387
-110
lines changed

client/client.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/plgd-dev/device/v2/internal/math"
3232
"github.com/plgd-dev/device/v2/pkg/log"
3333
"github.com/plgd-dev/device/v2/pkg/net/coap"
34+
"github.com/plgd-dev/device/v2/schema"
3435
"github.com/plgd-dev/go-coap/v3/net/blockwise"
3536
"github.com/plgd-dev/go-coap/v3/options"
3637
coapSync "github.com/plgd-dev/go-coap/v3/pkg/sync"
@@ -428,3 +429,28 @@ func NewDeviceOwnerFromConfig(cfg *Config, dialTLS core.DialTLS, dialDTLS core.D
428429
}
429430
return newDeviceOwnershipNone(), nil
430431
}
432+
433+
func (c *Client) GetDeviceLinkForHref(
434+
ctx context.Context,
435+
deviceID string,
436+
href string,
437+
discoveryCfg core.DiscoveryConfiguration,
438+
callback LinkNotFoundCallback,
439+
) (*core.Device, schema.ResourceLink, error) {
440+
d, links, err := c.GetDevice(ctx, deviceID, WithDiscoveryConfiguration(discoveryCfg))
441+
if err != nil {
442+
return nil, schema.ResourceLink{}, err
443+
}
444+
445+
link, err := core.GetResourceLink(links, href)
446+
if err != nil {
447+
if callback.linkNotFoundCallback == nil {
448+
return nil, schema.ResourceLink{}, err
449+
}
450+
link, err = callback.linkNotFoundCallback(links, href)
451+
if err != nil {
452+
return nil, schema.ResourceLink{}, err
453+
}
454+
}
455+
return d, link, nil
456+
}

client/createResource.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,7 @@ func (c *Client) CreateResource(
4545
cfg = o.applyOnCreate(cfg)
4646
}
4747

48-
d, links, err := c.GetDevice(ctx, deviceID, WithDiscoveryConfiguration(cfg.discoveryConfiguration))
49-
if err != nil {
50-
return err
51-
}
52-
53-
link, err := core.GetResourceLink(links, href)
48+
device, link, err := c.GetDeviceLinkForHref(ctx, deviceID, href, cfg.discoveryConfiguration, LinkNotFoundCallback{linkNotFoundCallback: cfg.linkNotFoundCallback})
5449
if err != nil {
5550
return err
5651
}
@@ -59,5 +54,5 @@ func (c *Client) CreateResource(
5954
cfg.opts = append(cfg.opts, coap.WithDeviceID(deviceID))
6055
}
6156

62-
return d.UpdateResourceWithCodec(ctx, link, cfg.codec, request, response, cfg.opts...)
57+
return device.UpdateResourceWithCodec(ctx, link, cfg.codec, request, response, cfg.opts...)
6358
}

client/createResource_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/plgd-dev/device/v2/client"
2424
"github.com/plgd-dev/device/v2/client/core"
25+
"github.com/plgd-dev/device/v2/schema"
2526
"github.com/plgd-dev/device/v2/schema/device"
2627
"github.com/plgd-dev/device/v2/schema/interfaces"
2728
"github.com/plgd-dev/device/v2/test"
@@ -61,6 +62,32 @@ func TestClientCreateResource(t *testing.T) {
6162
},
6263
}),
6364
},
65+
{
66+
name: "test link not found callback",
67+
args: args{
68+
deviceID: deviceID,
69+
href: "/link/not/found",
70+
body: test.MakeSwitchResourceDefaultData(),
71+
opts: []client.CreateOption{
72+
client.WithDiscoveryConfiguration(core.DefaultDiscoveryConfiguration()),
73+
client.WithLinkNotFoundCallback(func(links schema.ResourceLinks, href string) (schema.ResourceLink, error) {
74+
_, linkFound := links.GetResourceLink(href)
75+
require.False(t, linkFound)
76+
resourceLink, linkFound := links.GetResourceLink(test.TestResourceSwitchesHref)
77+
require.True(t, linkFound)
78+
return resourceLink, nil
79+
}),
80+
},
81+
},
82+
want: test.MakeSwitchResourceData(map[string]interface{}{
83+
"href": test.TestResourceSwitchesInstanceHref("2"),
84+
"rep": map[interface{}]interface{}{
85+
"if": []interface{}{interfaces.OC_IF_A, interfaces.OC_IF_BASELINE},
86+
"rt": []interface{}{types.BINARY_SWITCH},
87+
"value": false,
88+
},
89+
}),
90+
},
6491
{
6592
name: "invalid href",
6693
args: args{

client/deleteResource.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ func (c *Client) DeleteResource(
4141
cfg = o.applyOnDelete(cfg)
4242
}
4343

44-
d, links, err := c.GetDevice(ctx, deviceID, WithDiscoveryConfiguration(cfg.discoveryConfiguration))
45-
if err != nil {
46-
return err
47-
}
48-
49-
link, err := core.GetResourceLink(links, href)
44+
device, link, err := c.GetDeviceLinkForHref(ctx, deviceID, href, cfg.discoveryConfiguration, LinkNotFoundCallback{linkNotFoundCallback: cfg.linkNotFoundCallback})
5045
if err != nil {
5146
return err
5247
}
@@ -55,5 +50,5 @@ func (c *Client) DeleteResource(
5550
cfg.opts = append(cfg.opts, coap.WithDeviceID(deviceID))
5651
}
5752

58-
return d.DeleteResourceWithCodec(ctx, link, cfg.codec, response, cfg.opts...)
53+
return device.DeleteResourceWithCodec(ctx, link, cfg.codec, response, cfg.opts...)
5954
}

client/deleteResource_test.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/plgd-dev/device/v2/client"
2525
"github.com/plgd-dev/device/v2/client/core"
2626
"github.com/plgd-dev/device/v2/schema"
27+
"github.com/plgd-dev/device/v2/schema/configuration"
2728
"github.com/plgd-dev/device/v2/schema/device"
2829
"github.com/plgd-dev/device/v2/schema/interfaces"
2930
"github.com/plgd-dev/device/v2/schema/resources"
@@ -52,7 +53,28 @@ func createSwitches(ctx context.Context, t *testing.T, c *client.Client, deviceI
5253

5354
func TestClientDeleteResource(t *testing.T) {
5455
deviceID := test.MustFindDeviceByName(test.DevsimName)
55-
const switchID = "1"
56+
const (
57+
switchID_1 = "1"
58+
switchID_2 = "2"
59+
)
60+
61+
c, err := testClient.NewTestSecureClient()
62+
require.NoError(t, err)
63+
defer func() {
64+
errC := c.Close(context.Background())
65+
require.NoError(t, errC)
66+
}()
67+
ctx, cancel := context.WithTimeout(context.Background(), test.TestTimeout)
68+
defer cancel()
69+
deviceID, err = c.OwnDevice(ctx, deviceID, client.WithOTMs([]client.OTMType{client.OTMType_JustWorks}))
70+
require.NoError(t, err)
71+
defer disown(t, c, deviceID)
72+
73+
createSwitches(ctx, t, c, deviceID, 2)
74+
var nonDiscoverableResource map[string]interface{}
75+
err = c.CreateResource(ctx, deviceID, test.TestResourceSwitchesHref, test.MakeNonDiscoverableSwitchData(), &nonDiscoverableResource)
76+
require.NoError(t, err)
77+
5678
type args struct {
5779
deviceID string
5880
href string
@@ -67,10 +89,28 @@ func TestClientDeleteResource(t *testing.T) {
6789
name: "valid",
6890
args: args{
6991
deviceID: deviceID,
70-
href: test.TestResourceSwitchesInstanceHref(switchID),
92+
href: test.TestResourceSwitchesInstanceHref(switchID_1),
7193
opts: []client.DeleteOption{client.WithDiscoveryConfiguration(core.DefaultDiscoveryConfiguration())},
7294
},
7395
},
96+
{
97+
name: "delete non-discoverable resource",
98+
args: args{
99+
deviceID: deviceID,
100+
href: nonDiscoverableResource["href"].(string),
101+
opts: []client.DeleteOption{
102+
client.WithDiscoveryConfiguration(core.DefaultDiscoveryConfiguration()),
103+
// create the link for non-discoverable resource by utilizing the linkNotFoundCallback
104+
// as the only thing that we need in the link is the href and endpoints we will reuse
105+
// some known discoverable resource
106+
client.WithLinkNotFoundCallback(func(links schema.ResourceLinks, href string) (schema.ResourceLink, error) {
107+
resourceLink, _ := links.GetResourceLink(configuration.ResourceURI)
108+
resourceLink.Href = href
109+
return resourceLink, nil
110+
}),
111+
},
112+
},
113+
},
74114
{
75115
name: "invalid href",
76116
args: args{
@@ -89,20 +129,6 @@ func TestClientDeleteResource(t *testing.T) {
89129
},
90130
}
91131

92-
c, err := testClient.NewTestSecureClient()
93-
require.NoError(t, err)
94-
defer func() {
95-
errC := c.Close(context.Background())
96-
require.NoError(t, errC)
97-
}()
98-
ctx, cancel := context.WithTimeout(context.Background(), test.TestTimeout)
99-
defer cancel()
100-
deviceID, err = c.OwnDevice(ctx, deviceID, client.WithOTMs([]client.OTMType{client.OTMType_JustWorks}))
101-
require.NoError(t, err)
102-
defer disown(t, c, deviceID)
103-
104-
createSwitches(ctx, t, c, deviceID, 1)
105-
106132
for _, tt := range tests {
107133
t.Run(tt.name, func(t *testing.T) {
108134
err := c.DeleteResource(ctx, tt.args.deviceID, tt.args.href, nil, tt.args.opts...)

client/getResource.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,8 @@ func (c *Client) GetResource(
4040
for _, o := range opts {
4141
cfg = o.applyOnGet(cfg)
4242
}
43-
d, links, err := c.GetDevice(ctx, deviceID, WithDiscoveryConfiguration(cfg.discoveryConfiguration))
44-
if err != nil {
45-
return err
46-
}
4743

48-
link, err := core.GetResourceLink(links, href)
44+
device, link, err := c.GetDeviceLinkForHref(ctx, deviceID, href, cfg.discoveryConfiguration, LinkNotFoundCallback{linkNotFoundCallback: cfg.linkNotFoundCallback})
4945
if err != nil {
5046
return err
5147
}
@@ -54,5 +50,5 @@ func (c *Client) GetResource(
5450
cfg.opts = append(cfg.opts, coap.WithDeviceID(deviceID))
5551
}
5652

57-
return d.GetResourceWithCodec(ctx, link, cfg.codec, response, cfg.opts...)
53+
return device.GetResourceWithCodec(ctx, link, cfg.codec, response, cfg.opts...)
5854
}

client/getResource_test.go

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package client_test
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"testing"
2324

@@ -44,6 +45,22 @@ import (
4445

4546
func TestClientGetResource(t *testing.T) {
4647
deviceID := test.MustFindDeviceByName(test.DevsimName)
48+
c, err := testClient.NewTestSecureClient()
49+
require.NoError(t, err)
50+
defer func() {
51+
errC := c.Close(context.Background())
52+
require.NoError(t, errC)
53+
}()
54+
ctx, cancel := context.WithTimeout(context.Background(), test.TestTimeout)
55+
defer cancel()
56+
deviceID, err = c.OwnDevice(ctx, deviceID)
57+
require.NoError(t, err)
58+
defer disown(t, c, deviceID)
59+
60+
var nonDiscoverableResource map[string]interface{}
61+
err = c.CreateResource(ctx, deviceID, test.TestResourceSwitchesHref, test.MakeNonDiscoverableSwitchData(), &nonDiscoverableResource)
62+
require.NoError(t, err)
63+
4764
type args struct {
4865
deviceID string
4966
href string
@@ -86,6 +103,36 @@ func TestClientGetResource(t *testing.T) {
86103
},
87104
},
88105
},
106+
{
107+
name: "get non-discoverable resource",
108+
args: args{
109+
deviceID: deviceID,
110+
href: nonDiscoverableResource["href"].(string),
111+
opts: []client.GetOption{
112+
client.WithDiscoveryConfiguration(core.DefaultDiscoveryConfiguration()),
113+
// create the link for non-discoverable resource by utilizing the linkNotFoundCallback
114+
// as the only thing that we need in the link is the href and endpoints we will reuse
115+
// some known discoverable resource
116+
client.WithLinkNotFoundCallback(func(links schema.ResourceLinks, href string) (schema.ResourceLink, error) {
117+
resourceLink, ok := links.GetResourceLink(configuration.ResourceURI)
118+
if !ok {
119+
return schema.ResourceLink{}, errors.New("resource link not found: " + configuration.ResourceURI)
120+
}
121+
if len(resourceLink.Endpoints) == 0 {
122+
return schema.ResourceLink{}, errors.New("resource link has no endpoints")
123+
}
124+
resourceLink.Href = href
125+
return resourceLink, nil
126+
}),
127+
},
128+
},
129+
want: coap.DetailedResponse[interface{}]{
130+
Code: codes.Content,
131+
Body: map[interface{}]interface{}{
132+
"value": false,
133+
},
134+
},
135+
},
89136
{
90137
name: "invalid href",
91138
args: args{
@@ -104,17 +151,6 @@ func TestClientGetResource(t *testing.T) {
104151
},
105152
}
106153

107-
c, err := testClient.NewTestSecureClient()
108-
require.NoError(t, err)
109-
defer func() {
110-
errC := c.Close(context.Background())
111-
require.NoError(t, errC)
112-
}()
113-
ctx, cancel := context.WithTimeout(context.Background(), test.TestTimeout)
114-
defer cancel()
115-
deviceID, err = c.OwnDevice(ctx, deviceID)
116-
require.NoError(t, err)
117-
defer disown(t, c, deviceID)
118154
for _, tt := range tests {
119155
t.Run(tt.name, func(t *testing.T) {
120156
var got coap.DetailedResponse[interface{}]

client/observeResource.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,7 @@ func (c *Client) ObserveResource(
226226
return getObservationID(key, resourceObservationID.String()), nil
227227
}
228228

229-
d, links, err := c.GetDevice(ctx, deviceID, WithDiscoveryConfiguration(cfg.discoveryConfiguration))
230-
if err != nil {
231-
return "", err
232-
}
233-
234-
link, err := core.GetResourceLink(links, href)
229+
device, link, err := c.GetDeviceLinkForHref(ctx, deviceID, href, cfg.discoveryConfiguration, LinkNotFoundCallback{linkNotFoundCallback: cfg.linkNotFoundCallback})
235230
if err != nil {
236231
return "", err
237232
}
@@ -240,12 +235,12 @@ func (c *Client) ObserveResource(
240235
cfg.opts = append(cfg.opts, coap.WithDeviceID(deviceID))
241236
}
242237

243-
observationID, err = d.ObserveResourceWithCodec(ctx, link, observerCodec{contentFormat: cfg.codec.ContentFormat()}, h, cfg.opts...)
238+
observationID, err = device.ObserveResourceWithCodec(ctx, link, observerCodec{contentFormat: cfg.codec.ContentFormat()}, h, cfg.opts...)
244239
if err != nil {
245240
return "", err
246241
}
247242

248-
dev, _ := c.deviceCache.UpdateOrStoreDevice(d)
243+
dev, _ := c.deviceCache.UpdateOrStoreDevice(device)
249244
h.observationID = observationID
250245
h.device = dev
251246

0 commit comments

Comments
 (0)