Validate device creation.
Update our uuid package to the latest, which is now based on the GitHub
fork instead of the Google Code. Also, update our api package to its latest
version, which now needs the pqarrays package as a dependency.
We fleshed out the validateDeviceCreation. We now pass in the scopes we have
(for broad access control) and the user ID (for fine-grained access control).
This helper returns the first error it encounters, though it should probably
return a slice so we can return multiple errors all at once.
Before we even decode the request to create a Device, let's check if the user is
even logged in. If we can't ascertain that or they're not, there's no point in
even consuming the memory necessary to read the request, because we know we're
not going to use it anyways.
Finally actually validate the devices we're creating, and return an appropriate
error for each error we can get.
Also, the api.CheckScopes helper function now takes the scopes passed in as a
string slice, and we have an api.GetScopes helper function to retrieve the
scopes associated with the request. Let's not keep parsing that.
We need two new scopes to control access for device creation; ScopeImport lets
users import devices in and is pretty much admin access.
ScopeCreateOtherUserDevices allows a user to create Devices that are owned by
another user.
9 "golang.org/x/net/context"
11 "code.secondbit.org/uuid.hg"
15 // ErrDeviceNotFound is returned when the specified device couldn't be found.
16 ErrDeviceNotFound = errors.New("device not found")
20 // MinDeviceNameLength is the minimum length a Device name can be.
21 MinDeviceNameLength = 1
22 // MaxDeviceNameLength is the maximum length a Device name can be.
23 MaxDeviceNameLength = 64
26 // Device represents a specific device that updates can be pushed to.
37 // ApplyChange returns a Device that is a copy of the passed Device,
38 // but with the passed DeviceChange applied.
39 func ApplyChange(d Device, change DeviceChange) Device {
41 if change.Name != nil {
42 result.Name = *change.Name
44 if change.Owner != nil {
45 result.Owner = *change.Owner
47 // We don't want to accidentally leave a slice that
48 // is owned by both behind.
49 result.Owner = d.Owner.Copy()
51 if change.Type != nil {
52 result.Type = *change.Type
54 if change.LastSeen != nil {
55 result.LastSeen = *change.LastSeen
57 if change.PushToken != nil {
58 result.PushToken = *change.PushToken
63 // DeviceChange represents a set of changes to a Device that will be used
64 // to update a Device.
65 type DeviceChange struct {
74 // Storer is an interface to control how data is stored in and retrieved from
76 type Storer interface {
77 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error)
78 UpdateDevice(change DeviceChange, c context.Context) error
79 DeleteDevices(ids []uuid.ID, c context.Context) error
80 CreateDevices(devices []Device, c context.Context) error
81 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error)
84 // ErrDeviceAlreadyExists is returned when a Device is added to a Storer, but a
85 // Device with the same ID already exists in the Storer. The ID underlying
86 // ErrDeviceAlreadyExists will hold the ID that already exists.
87 type ErrDeviceAlreadyExists uuid.ID
89 func (e ErrDeviceAlreadyExists) Error() string {
90 return fmt.Sprintf("device with ID %s already exists in datastore", uuid.ID(e).String())
93 // GetMany returns as many of the Devices specified by the passed IDs as possible.
94 // They are returned as a map, with the key being the string version of the ID.
95 // No error will be returned if a Device can't be found.
96 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
97 results := map[string]Device{}
98 storer, err := GetStorer(c)
100 log.Printf("Error retrieving Storer: %+v\n", err)
103 results, err = storer.GetDevices(ids, c)
105 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err)
111 // Get returns the Device specified by the passed ID. If the Device can't be found,
112 // an ErrDeviceNotFound error is returned.
113 func Get(id uuid.ID, c context.Context) (Device, error) {
114 results, err := GetMany([]uuid.ID{id}, c)
118 result, ok := results[id.String()]
120 return Device{}, ErrDeviceNotFound
125 // Update applies the DeviceChange to the passed Device, and returns the result. If
126 // the Device can't be found, an ErrDeviceNotFound error was returned.
127 func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
128 storer, err := GetStorer(c)
130 log.Printf("Error retrieving Storer: %+v\n", err)
133 change.DeviceID = device.ID
134 err = storer.UpdateDevice(change, c)
138 return ApplyChange(device, change), nil
141 // DeleteMany removes the passed IDs from the datastore. No error is returned if the
142 // ID doesn't correspond to a Device in the datastore.
143 func DeleteMany(ids []uuid.ID, c context.Context) error {
144 storer, err := GetStorer(c)
146 log.Printf("Error retrieving Storer: %+v\n", err)
149 return storer.DeleteDevices(ids, c)
152 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't
153 // correspond to a Device in the datastore.
154 func Delete(id uuid.ID, c context.Context) error {
155 return DeleteMany([]uuid.ID{id}, c)
158 // CreateMany stores the passed Devices in the datastore, assigning default values if
159 // necessary. The Devices that were ultimately stored (including any default values, if
160 // applicable) are returned.
161 func CreateMany(devices []Device, c context.Context) ([]Device, error) {
162 storer, err := GetStorer(c)
164 log.Printf("Error retrieving Storer: %+v\n", err)
165 return []Device{}, err
167 modified := make([]Device, 0, len(devices))
168 for _, device := range devices {
169 if device.ID.IsZero() {
170 device.ID = uuid.NewID()
172 if device.Created.IsZero() {
173 device.Created = time.Now()
175 if device.LastSeen.IsZero() {
176 device.LastSeen = time.Now()
178 modified = append(modified, device)
180 err = storer.CreateDevices(modified, c)
182 return []Device{}, err
187 // Create stores the passed Device in the datastore, assigning default values if
188 // necessary. The Devices that were ultimately stored (including any default values, if
189 // applicable) are returned.
190 func Create(device Device, c context.Context) (Device, error) {
191 devices, err := CreateMany([]Device{device}, c)
195 // There should never be a case where we don't return a result.
196 // Ideally, we'd return an error here instead of letting the panic
197 // happen, but seeing as I can't come up with a reason the error would
198 // occur, I'm having trouble coming up with a reasonable error to return.
199 return devices[0], nil
202 // ListByOwner returns a slice of all the Devices with an Owner property that
203 // matches the passed ID. There's no guarantee on the order the Devices will be
205 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
206 // 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.
207 storer, err := GetStorer(c)
209 log.Printf("Error retrieving Storer: %+v\n", err)
210 return []Device{}, err
212 devices, err := storer.ListDevicesByOwner(user, c)
216 // ToMap transforms the passed slice into a map by using the String method of
217 // each Device's ID as the key, and the Device as the value.
218 func ToMap(devices []Device) map[string]Device {
219 results := make(map[string]Device, len(devices))
220 for _, device := range devices {
221 results[device.ID.String()] = device
226 // ToSlice transforms the passed map of Devices into a slice of Devices.
227 func ToSlice(devices map[string]Device) []Device {
228 results := make([]Device, 0, len(devices))
229 for _, device := range devices {
230 results = append(results, device)