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