ducky/devices

Paddy 2015-11-27 Parent:f5a9d5f8f28d Child:03c49b4d3d9f

11:683050b4546b Go to Latest

ducky/devices/devices.go

Return ErrDeviceNotFound when updating devices. If we can't find the Device we're supposed to update, return an ErrDeviceNotFound error. Write a unit test that tests for this behaviour.

History
1 package devices
3 import (
4 "errors"
5 "fmt"
6 "log"
7 "time"
9 "golang.org/x/net/context"
11 "code.secondbit.org/uuid.hg"
12 )
14 var (
15 // ErrDeviceNotFound is returned when the specified device couldn't be found.
16 ErrDeviceNotFound = errors.New("device not found")
17 )
19 // Device represents a specific device that updates can be pushed to.
20 type Device struct {
21 ID uuid.ID
22 Name string
23 Owner uuid.ID
24 Type DeviceType
25 Created time.Time
26 LastSeen time.Time
27 PushToken string
28 }
30 // ApplyChange returns a Device that is a copy of the passed Device,
31 // but with the passed DeviceChange applied.
32 func ApplyChange(d Device, change DeviceChange) Device {
33 result := d
34 if change.Name != nil {
35 result.Name = *change.Name
36 }
37 if change.Owner != nil {
38 result.Owner = *change.Owner
39 } else {
40 // We don't want to accidentally leave a slice that
41 // is owned by both behind.
42 result.Owner = d.Owner.Copy()
43 }
44 if change.Type != nil {
45 result.Type = *change.Type
46 }
47 if change.Created != nil {
48 result.Created = *change.Created
49 }
50 if change.LastSeen != nil {
51 result.LastSeen = *change.LastSeen
52 }
53 if change.PushToken != nil {
54 result.PushToken = *change.PushToken
55 }
56 return result
57 }
59 // DeviceChange represents a set of changes to a Device that will be used
60 // to update a Device.
61 type DeviceChange struct {
62 DeviceID uuid.ID
63 Name *string
64 Owner *uuid.ID
65 Type *DeviceType
66 Created *time.Time
67 LastSeen *time.Time
68 PushToken *string
69 }
71 // Storer is an interface to control how data is stored in and retrieved from
72 // the datastore.
73 type Storer interface {
74 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
75 UpdateDevice(change DeviceChange, c context.Context) error
76 DeleteDevices(ids []uuid.ID, c context.Context) error
77 CreateDevices(devices []Device, c context.Context) error
78 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
79 }
81 type ErrDeviceAlreadyExists uuid.ID
83 func (e ErrDeviceAlreadyExists) Error() string {
84 return fmt.Sprintf("device with ID %s already exists in datastore", uuid.ID(e).String())
85 }
87 // GetMany returns as many of the Devices specified by the passed IDs as possible.
88 // They are returned as a map, with the key being the string version of the ID.
89 // No error will be returned if a Device can't be found.
90 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
91 results := map[string]Device{}
92 storer, err := getStorer(c)
93 if err != nil {
94 log.Printf("Error retrieving Storer: %+v\n", err)
95 return results, err
96 }
97 results, err = storer.GetDevices(ids, c)
98 if err != nil {
99 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
100 return results, err
101 }
102 return results, nil
103 }
105 // Get returns the Device specified by the passed ID. If the Device can't be found,
106 // an ErrDeviceNotFound error is returned.
107 func Get(id uuid.ID, c context.Context) (Device, error) {
108 results, err := GetMany([]uuid.ID{id}, c)
109 if err != nil {
110 return Device{}, err
111 }
112 result, ok := results[id.String()]
113 if !ok {
114 return Device{}, ErrDeviceNotFound
115 }
116 return result, nil
117 }
119 // Update applies the DeviceChange to the passed Device, and returns the result. If
120 // the Device can't be found, an ErrDeviceNotFound error was returned.
121 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
122 storer, err := getStorer(c)
123 if err != nil {
124 log.Printf("Error retrieving Storer: %+v\n", err)
125 return Device{}, err
126 }
127 change.DeviceID = device.ID
128 err = storer.UpdateDevice(change, c)
129 if err != nil {
130 return Device{}, err
131 }
132 return ApplyChange(device, change), nil
133 }
135 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
136 // ID doesn't correspond to a Device in the datastore.
137 func DeleteMany(ids []uuid.ID, c context.Context) error {
138 storer, err := getStorer(c)
139 if err != nil {
140 log.Printf("Error retrieving Storer: %+v\n", err)
141 return err
142 }
143 return storer.DeleteDevices(ids, c)
144 }
146 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
147 // correspond to a Device in the datastore.
148 func Delete(id uuid.ID, c context.Context) error {
149 return DeleteMany([]uuid.ID{id}, c)
150 }
152 // CreateMany stores the passed Devices in the datastore, assigning default values if
153 // necessary. The Devices that were ultimately stored (including any default values, if
154 // applicable) are returned.
155 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
156 storer, err := getStorer(c)
157 if err != nil {
158 log.Printf("Error retrieving Storer: %+v\n", err)
159 return []Device{}, err
160 }
161 modified := make([]Device, 0, len(devices))
162 for _, device := range devices {
163 if device.ID.IsZero() {
164 device.ID = uuid.NewID()
165 }
166 if device.Created.IsZero() {
167 device.Created = time.Now()
168 }
169 if device.LastSeen.IsZero() {
170 device.LastSeen = time.Now()
171 }
172 modified = append(modified, device)
173 }
174 err = storer.CreateDevices(devices, c)
175 if err != nil {
176 return []Device{}, err
177 }
178 return modified, nil
179 }
181 // Create stores the passed Device in the datastore, assigning default values if
182 // necessary. The Devices that were ultimately stored (including any default values, if
183 // applicable) are returned.
184 func Create(device Device, c context.Context) (Device, error) {
185 devices, err := CreateMany([]Device{device}, c)
186 if err != nil {
187 return Device{}, err
188 }
189 // There should never be a case where we don't return a result.
190 // Ideally, we'd return an error here instead of letting the panic
191 // happen, but seeing as I can't come up with a reason the error would
192 // occur, I'm having trouble coming up with a reasonable error to return.
193 return devices[0], nil
194 }
196 // ListByOwner returns a slice of all the Devices with an Owner property that
197 // matches the passed ID. There's no guarantee on the order the Devices will be
198 // returned in.
199 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
200 // BUG(paddy): Eventually, we'll need to support paging for devices. But right now, I don't foresee any user creating enough of them to make pagination worthwhile.
201 storer, err := getStorer(c)
202 if err != nil {
203 log.Printf("Error retrieving Storer: %+v\n", err)
204 return []Device{}, err
205 }
206 devices, err := storer.ListDevicesByOwner(user, c)
207 return devices, err
208 }
210 func ToMap(devices []Device) map[string]Device {
211 results := make(map[string]Device, len(devices))
212 for _, device := range devices {
213 results[device.ID.String()] = device
214 }
215 return results
216 }
218 func ToSlice(devices map[string]Device) []Device {
219 results := make([]Device, 0, len(devices))
220 for _, device := range devices {
221 results = append(results, device)
222 }
223 return results
224 }