diff --git a/examples/shadow/main.go b/examples/shadow/main.go index ed8a7e03..2b24abb3 100644 --- a/examples/shadow/main.go +++ b/examples/shadow/main.go @@ -21,7 +21,7 @@ import ( "time" "github.com/at-wat/mqtt-go" - "github.com/seqsense/aws-iot-device-sdk-go/v4" + awsiotdev "github.com/seqsense/aws-iot-device-sdk-go/v4" "github.com/seqsense/aws-iot-device-sdk-go/v4/shadow" ) @@ -29,8 +29,8 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if len(os.Args) != 3 { - println("usage: shadow AWS_IOT_ENDPOINT THING_NAME") + if len(os.Args) != 4 { + println("usage: shadow AWS_IOT_ENDPOINT THING_NAME SHADOW_NAME") println("") println("This example updates and deletes AWS IoT Thing Shadow.") println("THING_NAME must be registered to your account of AWS IoT beforehand.") @@ -43,6 +43,7 @@ func main() { } host := os.Args[1] thingName := os.Args[2] + shadowName := os.Args[3] for _, file := range []string{ "root-CA.crt", @@ -88,7 +89,7 @@ func main() { panic(err) } - s, err := shadow.New(ctx, cli) + s, err := shadow.New(ctx, cli, shadow.WithName(shadowName)) if err != nil { panic(err) } diff --git a/shadow/option.go b/shadow/option.go new file mode 100644 index 00000000..342c4db6 --- /dev/null +++ b/shadow/option.go @@ -0,0 +1,31 @@ +// Copyright 2020-2021 SEQSENSE, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shadow + +// Options stores Device Shadow options. +type Options struct { + Name string +} + +// Option is a functional option of UpdateJob. +type Option func(options *Options) error + +// WithName sets shadow name. +func WithName(name string) Option { + return func(o *Options) error { + o.Name = name + return nil + } +} diff --git a/shadow/shadow.go b/shadow/shadow.go index a5355023..2004c6bc 100644 --- a/shadow/shadow.go +++ b/shadow/shadow.go @@ -24,7 +24,7 @@ import ( "github.com/at-wat/mqtt-go" - "github.com/seqsense/aws-iot-device-sdk-go/v4" + awsiotdev "github.com/seqsense/aws-iot-device-sdk-go/v4" "github.com/seqsense/aws-iot-device-sdk-go/v4/internal/ioterr" ) @@ -57,6 +57,7 @@ type shadow struct { mqtt.ServeMux cli mqtt.Client thingName string + name string doc *ThingDocument mu sync.Mutex onDelta func(delta map[string]interface{}) @@ -73,7 +74,11 @@ func (s *shadow) token() string { } func (s *shadow) topic(operation string) string { - return "$aws/things/" + s.thingName + "/shadow/" + operation + prefix := "$aws/things/" + s.thingName + "/shadow" + if s.name != "" { + prefix += "/name/" + s.name + } + return prefix + "/" + operation } func (s *shadow) handleResponse(r interface{}) { @@ -160,10 +165,17 @@ func (s *shadow) deleteAccepted(msg *mqtt.Message) { } // New creates Thing Shadow interface. -func New(ctx context.Context, cli awsiotdev.Device) (Shadow, error) { +func New(ctx context.Context, cli awsiotdev.Device, opt ...Option) (Shadow, error) { + opts := &Options{} + for _, o := range opt { + if err := o(opts); err != nil { + return nil, ioterr.New(err, "applying option") + } + } s := &shadow{ cli: cli, thingName: cli.ThingName(), + name: opts.Name, doc: &ThingDocument{ State: ThingState{ Desired: map[string]interface{}{}, diff --git a/shadow/shadow_test.go b/shadow/shadow_test.go index 933bbff7..c8a9a417 100644 --- a/shadow/shadow_test.go +++ b/shadow/shadow_test.go @@ -57,6 +57,44 @@ func TestNew(t *testing.T) { t.Errorf("Expected error: %v, got: %v", errDummy, err) } }) + + t.Run("Options", func(t *testing.T) { + t.Run("WithName", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + cli := &mockDevice{&mockmqtt.Client{}} + operation := "foo/bar" + + testCases := map[string]struct { + input string + expected string + }{ + "ClassicShadow": { + input: "", + expected: "$aws/things/" + cli.ThingName() + "/shadow/" + operation, + }, + "NamedShadow": { + input: "testShadow", + expected: "$aws/things/" + cli.ThingName() + "/shadow/name/testShadow/" + operation, + }, + } + + for name, testCase := range testCases { + testCase := testCase + t.Run(name, func(t *testing.T) { + s, err := New(ctx, cli, WithName(testCase.input)) + if err != nil { + t.Fatal(err) + } + topic := s.(*shadow).topic(operation) + if topic != testCase.expected { + t.Errorf("Expected topic name: %v, got: %v", testCase.expected, topic) + } + }) + } + }) + }) } func TestHandlers(t *testing.T) {