ducky/devices

Paddy 2015-12-14 Parent:1ae5bae472c1 Child:a700ede02f91

15:c24a6c5fcd8c Browse Files

Begin implementation on apiv1. Begin implementing the apiv1 package, which will define the first iteration of our API endpoints and logic. Each API package should be self-contained and able to run without depending on each other. Think of them as interfaces into manipulating the business logic defined in the devices package. The point is to have total control over backwards compatibility, as long as our business logic doesn't change. If that happens, we're in a bad place, but not as bad as it could be. This required us to pull in all our API tools; the api package, its dependencies, the scopeTypes package (so we can define scopes for our API), the trout router, etc. We also updated uuid to the latest, which now includes a license. Hooray? The new apiv1 package consists of a few things: * The devices.go file defines the types the API will use to communicate, along with some helpers to convert from API types to devices types. There's also a stub for validating the device creation requests, which I haven't implemented yet because I'm a pretty bad person. * endpoints.go just contains a helper function that builds our routes and assigns handlers to them, giving us an http.Handler in the returns that we can listen with. * handlers.go defines our HTTP handlers, which will read requests and write responses, after doing the appropriate validation and executing the appropriating business logic. Right now, we only have a handler for creating devices, and it doesn't actually do any validation. Also, we have some user-correctable errors being returned as 500s right now, which is Bad. Fortunately, they're all marked with BUG, so I can at least come back to them. * response.go defines the Response type that will be used for returning information after a request is executed. It may eventually get some helpers, but for now it's pretty basic. * scopes.go defines the Scopes that we're going to be using in the package to control access. It should probably (eventually) include a helper to register the Scopes, or we should have a collector service that pulls in all the packages, finds all their Scopes, and registers them. I haven't decided how I want to manage Scope registration just yet. We exported the getStorer function (now GetStorer) so other packages can use it. I'm not sure how I feel about this just yet. We also had to create a WithStorer helper method that embeds the Storer into a context.Context, so we can bootstrap in devicesd. We erroneously had Created in the DeviceChange struct, but there's no reason the Created property of a Device should ever change, so it was removed from the logic, from the struct, and from the tests. Our CreateMany helper was erroneously creating the un-modified Devices that were passed in, instead of the Devices that had sensible defaults filled. We created a _very minimal_ (e.g., needs some work before it's ready for production) devicesd package that will spin up a simple server, just so we could take a peek at our apiv1 endpoints as they'd actually be used. (It worked. Yay?) We should continue to expand on this with configuration, more information being logged, etc.

.hgignore Godeps/Godeps.json apiv1/devices.go apiv1/endpoints.go apiv1/handlers.go apiv1/response.go apiv1/scopes.go context.go devices.go devicesd/server.go storer_test.go vendor/bitbucket.org/ww/goautoneg/Makefile vendor/bitbucket.org/ww/goautoneg/README.txt vendor/bitbucket.org/ww/goautoneg/autoneg.go vendor/code.secondbit.org/api.hg/api.go vendor/code.secondbit.org/scopes.hg/types/scope.go vendor/code.secondbit.org/scopes.hg/types/scope_postgres.go vendor/code.secondbit.org/trout.hg/LICENSE vendor/code.secondbit.org/trout.hg/doc.go vendor/code.secondbit.org/trout.hg/route.go vendor/code.secondbit.org/trout.hg/trie.go vendor/code.secondbit.org/uuid.hg/LICENSE

     1.1 --- a/.hgignore	Sun Nov 29 21:31:47 2015 -0800
     1.2 +++ b/.hgignore	Mon Dec 14 00:12:33 2015 -0800
     1.3 @@ -1,1 +1,2 @@
     1.4  cover.out
     1.5 +devicesd/devicesd
     2.1 --- a/Godeps/Godeps.json	Sun Nov 29 21:31:47 2015 -0800
     2.2 +++ b/Godeps/Godeps.json	Mon Dec 14 00:12:33 2015 -0800
     2.3 @@ -2,18 +2,38 @@
     2.4  	"ImportPath": "code.secondbit.org/ducky/devices.hg",
     2.5  	"GoVersion": "go1.5.1",
     2.6  	"Packages": [
     2.7 -		"."
     2.8 +		"./..."
     2.9  	],
    2.10  	"Deps": [
    2.11  		{
    2.12 +			"ImportPath": "bitbucket.org/ww/goautoneg",
    2.13 +			"Comment": "null-5",
    2.14 +			"Rev": "'75cd24fc2f2c2a2088577d12123ddee5f54e0675'"
    2.15 +		},
    2.16 +		{
    2.17  			"ImportPath": "code.google.com/p/go-uuid/uuid",
    2.18  			"Comment": "null-15",
    2.19  			"Rev": "'35bc42037350f0078e3c974c6ea690f1926603ab'"
    2.20  		},
    2.21  		{
    2.22 +			"ImportPath": "code.secondbit.org/api.hg",
    2.23 +			"Comment": "null-2",
    2.24 +			"Rev": "'57c9412e80007115060984bcc29fdc24418ff5ce'"
    2.25 +		},
    2.26 +		{
    2.27 +			"ImportPath": "code.secondbit.org/scopes.hg/types",
    2.28 +			"Comment": "null-3",
    2.29 +			"Rev": "'a64a25ae2db1fe949b0ea2f552774bc3544613af'"
    2.30 +		},
    2.31 +		{
    2.32 +			"ImportPath": "code.secondbit.org/trout.hg",
    2.33 +			"Comment": "null-3",
    2.34 +			"Rev": "'b966f38379ddb51f07e8adeb121599e107998409'"
    2.35 +		},
    2.36 +		{
    2.37  			"ImportPath": "code.secondbit.org/uuid.hg",
    2.38 -			"Comment": "null-5",
    2.39 -			"Rev": "'cda03b52c8c99d986df4e7601c7b071bb7bd6333'"
    2.40 +			"Comment": "null-6",
    2.41 +			"Rev": "'6b0a9efd13182f3a12bf8f79e01ba19e75a5076e'"
    2.42  		},
    2.43  		{
    2.44  			"ImportPath": "golang.org/x/net/context",
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/apiv1/devices.go	Mon Dec 14 00:12:33 2015 -0800
     3.3 @@ -0,0 +1,72 @@
     3.4 +package apiv1
     3.5 +
     3.6 +import (
     3.7 +	"time"
     3.8 +
     3.9 +	"code.secondbit.org/ducky/devices.hg"
    3.10 +	"code.secondbit.org/uuid.hg"
    3.11 +)
    3.12 +
    3.13 +// Device represents a device as exposed through the API. It is its
    3.14 +// own type as the business logic and the API have different requirements
    3.15 +// for the Device type, and require different representations.
    3.16 +type Device struct {
    3.17 +	ID        uuid.ID            `json:"id"`
    3.18 +	Name      string             `json:"name,omitempty"`
    3.19 +	Owner     uuid.ID            `json:"owner,omitempty"`
    3.20 +	Type      devices.DeviceType `json:"type,omitempty"`
    3.21 +	Created   time.Time          `json:"created,omitempty"`
    3.22 +	LastSeen  time.Time          `json:"lastSeen,omitempty"`
    3.23 +	PushToken string             `json:"pushToken,omitempty"`
    3.24 +}
    3.25 +
    3.26 +// DeviceChange represents a set of changes to a device that will be used
    3.27 +// to update that device. It is its own type as the business logic and the
    3.28 +// API have different requirements for the DeviceChange type, and require
    3.29 +// different representations.
    3.30 +type DeviceChange struct {
    3.31 +	DeviceID  uuid.ID             `json:"id"`
    3.32 +	Name      *string             `json:"name,omitempty"`
    3.33 +	Owner     *uuid.ID            `json:"owner,omitempty"`
    3.34 +	Type      *devices.DeviceType `json:"type,omitempty"`
    3.35 +	LastSeen  *time.Time          `json:"lastSeen,omitempty"`
    3.36 +	PushToken *string             `json:"pushToken,omitempty"`
    3.37 +}
    3.38 +
    3.39 +func apiDeviceFromCore(d devices.Device, includePushToken bool) Device {
    3.40 +	device := Device{
    3.41 +		ID:       d.ID,
    3.42 +		Name:     d.Name,
    3.43 +		Owner:    d.Owner,
    3.44 +		Type:     d.Type,
    3.45 +		Created:  d.Created,
    3.46 +		LastSeen: d.LastSeen,
    3.47 +	}
    3.48 +	if includePushToken {
    3.49 +		device.PushToken = d.PushToken
    3.50 +	}
    3.51 +	return device
    3.52 +}
    3.53 +
    3.54 +func changeFromAPI(d DeviceChange) devices.DeviceChange {
    3.55 +	return devices.DeviceChange{
    3.56 +		DeviceID:  d.DeviceID,
    3.57 +		Name:      d.Name,
    3.58 +		Owner:     d.Owner,
    3.59 +		Type:      d.Type,
    3.60 +		LastSeen:  d.LastSeen,
    3.61 +		PushToken: d.PushToken,
    3.62 +	}
    3.63 +}
    3.64 +
    3.65 +func createDevicesFromChanges(changes []DeviceChange) []devices.Device {
    3.66 +	newDevices := make([]devices.Device, 0, len(changes))
    3.67 +	for _, change := range changes {
    3.68 +		newDevices = append(newDevices, devices.ApplyChange(devices.Device{}, changeFromAPI(change)))
    3.69 +	}
    3.70 +	return newDevices
    3.71 +}
    3.72 +
    3.73 +func validateDeviceCreation(d devices.Device) error {
    3.74 +	return nil
    3.75 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/apiv1/endpoints.go	Mon Dec 14 00:12:33 2015 -0800
     4.3 @@ -0,0 +1,18 @@
     4.4 +package apiv1
     4.5 +
     4.6 +import (
     4.7 +	"net/http"
     4.8 +
     4.9 +	"code.secondbit.org/api.hg"
    4.10 +	"code.secondbit.org/trout.hg"
    4.11 +	"golang.org/x/net/context"
    4.12 +)
    4.13 +
    4.14 +// GetRouter returns a trout Router that is configured to handle
    4.15 +// all the routes necessary to serve a devices API server.
    4.16 +func GetRouter(ctx context.Context) http.Handler {
    4.17 +	var router trout.Router
    4.18 +	router.Endpoint("/").Methods("POST").Handler(api.ContextWrapper(ctx, api.ContextHandler(handleCreateDevices)))
    4.19 +
    4.20 +	return router
    4.21 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/apiv1/handlers.go	Mon Dec 14 00:12:33 2015 -0800
     5.3 @@ -0,0 +1,45 @@
     5.4 +package apiv1
     5.5 +
     5.6 +import (
     5.7 +	"log"
     5.8 +	"net/http"
     5.9 +
    5.10 +	"code.secondbit.org/api.hg"
    5.11 +	"code.secondbit.org/ducky/devices.hg"
    5.12 +
    5.13 +	"golang.org/x/net/context"
    5.14 +)
    5.15 +
    5.16 +type createRequest struct {
    5.17 +	Devices []DeviceChange `json:"devices"`
    5.18 +}
    5.19 +
    5.20 +func handleCreateDevices(ctx context.Context, w http.ResponseWriter, r *http.Request) {
    5.21 +	var req createRequest
    5.22 +	var resp Response
    5.23 +	err := api.Decode(r, &req)
    5.24 +	if err != nil {
    5.25 +		api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
    5.26 +		return
    5.27 +	}
    5.28 +
    5.29 +	devicesToCreate := createDevicesFromChanges(req.Devices)
    5.30 +	for _, device := range devicesToCreate {
    5.31 +		err := validateDeviceCreation(device)
    5.32 +		if err != nil {
    5.33 +			// BUG(paddy): We still need to expose the validation error
    5.34 +			return
    5.35 +		}
    5.36 +	}
    5.37 +	createdDevices, err := devices.CreateMany(devicesToCreate, ctx)
    5.38 +	if err != nil {
    5.39 +		// BUG(paddy): we should filter out non-internal errors here and expose better error responses
    5.40 +		log.Printf("Error creating devices: %+v\n", err)
    5.41 +		api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
    5.42 +		return
    5.43 +	}
    5.44 +	for _, device := range createdDevices {
    5.45 +		resp.Devices = append(resp.Devices, apiDeviceFromCore(device, api.CheckScopes(r, ScopeViewPushToken.ID)))
    5.46 +	}
    5.47 +	api.Encode(w, r, http.StatusCreated, resp)
    5.48 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/apiv1/response.go	Mon Dec 14 00:12:33 2015 -0800
     6.3 @@ -0,0 +1,12 @@
     6.4 +package apiv1
     6.5 +
     6.6 +import (
     6.7 +	"code.secondbit.org/api.hg"
     6.8 +)
     6.9 +
    6.10 +// Response is used to structure the output returned as HTTP responses
    6.11 +// to requests.
    6.12 +type Response struct {
    6.13 +	Devices []Device           `json:"devices,omitempty"`
    6.14 +	Errors  []api.RequestError `json:"errors,omitempty"`
    6.15 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/apiv1/scopes.go	Mon Dec 14 00:12:33 2015 -0800
     7.3 @@ -0,0 +1,13 @@
     7.4 +package apiv1
     7.5 +
     7.6 +import "code.secondbit.org/scopes.hg/types"
     7.7 +
     7.8 +var (
     7.9 +	// ScopeViewPushToken is a Scope that grants access to viewing pushTokens for
    7.10 +	// Devices.
    7.11 +	ScopeViewPushToken = scopeTypes.Scope{
    7.12 +		ID:          "https://scopes.useducky.com/devices/pushToken/view",
    7.13 +		Name:        "View device push tokens.",
    7.14 +		Description: "View the push tokens that allow sending messages and notifications to your device. This can be used to force your device to open links, and should be granted with extreme caution.",
    7.15 +	}
    7.16 +)
     8.1 --- a/context.go	Sun Nov 29 21:31:47 2015 -0800
     8.2 +++ b/context.go	Mon Dec 14 00:12:33 2015 -0800
     8.3 @@ -17,7 +17,11 @@
     8.4  	ErrStorerKeyNotStorer = errors.New("the value for storerKey does not fulfill the Storer interface")
     8.5  )
     8.6  
     8.7 -func getStorer(c context.Context) (Storer, error) {
     8.8 +// GetStorer returns the Storer associated with the passed Context.
     8.9 +// If no Storer is set, ErrNoStorerSet will be returned.
    8.10 +// If something that is not a Storer is set using the Storer's key,
    8.11 +// ErrStorerKeyNotStorer will be returned.
    8.12 +func GetStorer(c context.Context) (Storer, error) {
    8.13  	val := c.Value(storerKey)
    8.14  	if val == nil {
    8.15  		return nil, ErrNoStorerSet
    8.16 @@ -28,3 +32,9 @@
    8.17  	}
    8.18  	return storer, nil
    8.19  }
    8.20 +
    8.21 +// WithStorer returns a Context that extends from the passed Context,
    8.22 +// but includes or overwrites the Storer key with the passed Storer.
    8.23 +func WithStorer(c context.Context, storer Storer) context.Context {
    8.24 +	return context.WithValue(c, storerKey, storer)
    8.25 +}
     9.1 --- a/devices.go	Sun Nov 29 21:31:47 2015 -0800
     9.2 +++ b/devices.go	Mon Dec 14 00:12:33 2015 -0800
     9.3 @@ -44,9 +44,6 @@
     9.4  	if change.Type != nil {
     9.5  		result.Type = *change.Type
     9.6  	}
     9.7 -	if change.Created != nil {
     9.8 -		result.Created = *change.Created
     9.9 -	}
    9.10  	if change.LastSeen != nil {
    9.11  		result.LastSeen = *change.LastSeen
    9.12  	}
    9.13 @@ -63,7 +60,6 @@
    9.14  	Name      *string
    9.15  	Owner     *uuid.ID
    9.16  	Type      *DeviceType
    9.17 -	Created   *time.Time
    9.18  	LastSeen  *time.Time
    9.19  	PushToken *string
    9.20  }
    9.21 @@ -92,7 +88,7 @@
    9.22  // No error will be returned if a Device can't be found.
    9.23  func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) {
    9.24  	results := map[string]Device{}
    9.25 -	storer, err := getStorer(c)
    9.26 +	storer, err := GetStorer(c)
    9.27  	if err != nil {
    9.28  		log.Printf("Error retrieving Storer: %+v\n", err)
    9.29  		return results, err
    9.30 @@ -122,7 +118,7 @@
    9.31  // Update applies the DeviceChange to the passed Device, and returns the result. If
    9.32  // the Device can't be found, an ErrDeviceNotFound error was returned.
    9.33  func Update(device Device, change DeviceChange, c context.Context) (Device, error) {
    9.34 -	storer, err := getStorer(c)
    9.35 +	storer, err := GetStorer(c)
    9.36  	if err != nil {
    9.37  		log.Printf("Error retrieving Storer: %+v\n", err)
    9.38  		return Device{}, err
    9.39 @@ -138,7 +134,7 @@
    9.40  // DeleteMany removes the passed IDs from the datastore. No error is returned if the
    9.41  // ID doesn't correspond to a Device in the datastore.
    9.42  func DeleteMany(ids []uuid.ID, c context.Context) error {
    9.43 -	storer, err := getStorer(c)
    9.44 +	storer, err := GetStorer(c)
    9.45  	if err != nil {
    9.46  		log.Printf("Error retrieving Storer: %+v\n", err)
    9.47  		return err
    9.48 @@ -156,7 +152,7 @@
    9.49  // necessary. The Devices that were ultimately stored (including any default values, if
    9.50  // applicable) are returned.
    9.51  func CreateMany(devices []Device, c context.Context) ([]Device, error) {
    9.52 -	storer, err := getStorer(c)
    9.53 +	storer, err := GetStorer(c)
    9.54  	if err != nil {
    9.55  		log.Printf("Error retrieving Storer: %+v\n", err)
    9.56  		return []Device{}, err
    9.57 @@ -174,7 +170,7 @@
    9.58  		}
    9.59  		modified = append(modified, device)
    9.60  	}
    9.61 -	err = storer.CreateDevices(devices, c)
    9.62 +	err = storer.CreateDevices(modified, c)
    9.63  	if err != nil {
    9.64  		return []Device{}, err
    9.65  	}
    9.66 @@ -201,7 +197,7 @@
    9.67  // returned in.
    9.68  func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) {
    9.69  	// 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.
    9.70 -	storer, err := getStorer(c)
    9.71 +	storer, err := GetStorer(c)
    9.72  	if err != nil {
    9.73  		log.Printf("Error retrieving Storer: %+v\n", err)
    9.74  		return []Device{}, err
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/devicesd/server.go	Mon Dec 14 00:12:33 2015 -0800
    10.3 @@ -0,0 +1,22 @@
    10.4 +package main
    10.5 +
    10.6 +import (
    10.7 +	"net/http"
    10.8 +
    10.9 +	"code.secondbit.org/ducky/devices.hg"
   10.10 +	"code.secondbit.org/ducky/devices.hg/apiv1"
   10.11 +	"golang.org/x/net/context"
   10.12 +)
   10.13 +
   10.14 +func main() {
   10.15 +	ctx := devices.WithStorer(context.Background(), devices.NewMemstore())
   10.16 +
   10.17 +	router := apiv1.GetRouter(ctx)
   10.18 +
   10.19 +	http.Handle("/", router)
   10.20 +
   10.21 +	err := http.ListenAndServe(":9000", nil)
   10.22 +	if err != nil {
   10.23 +		panic(err)
   10.24 +	}
   10.25 +}
    11.1 --- a/storer_test.go	Sun Nov 29 21:31:47 2015 -0800
    11.2 +++ b/storer_test.go	Mon Dec 14 00:12:33 2015 -0800
    11.3 @@ -20,7 +20,6 @@
    11.4  	changeName = 1 << iota
    11.5  	changeOwner
    11.6  	changeType
    11.7 -	changeCreated
    11.8  	changeLastSeen
    11.9  	changePushToken
   11.10  	changeVariations
   11.11 @@ -251,7 +250,7 @@
   11.12  			var change DeviceChange
   11.13  			var owner uuid.ID
   11.14  			var name, pushToken string
   11.15 -			var created, lastSeen time.Time
   11.16 +			var lastSeen time.Time
   11.17  			var deviceType DeviceType
   11.18  
   11.19  			device.ID = uuid.NewID()
   11.20 @@ -276,11 +275,6 @@
   11.21  				change.Type = &deviceType
   11.22  				expectation.Type = deviceType
   11.23  			}
   11.24 -			if i&changeCreated != 0 {
   11.25 -				created = time.Now().Add(time.Hour * time.Duration(i))
   11.26 -				change.Created = &created
   11.27 -				expectation.Created = created
   11.28 -			}
   11.29  			if i&changeLastSeen != 0 {
   11.30  				lastSeen = time.Now().Add(time.Minute * time.Duration(i*-1))
   11.31  				change.LastSeen = &lastSeen
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/vendor/bitbucket.org/ww/goautoneg/Makefile	Mon Dec 14 00:12:33 2015 -0800
    12.3 @@ -0,0 +1,13 @@
    12.4 +include $(GOROOT)/src/Make.inc
    12.5 +
    12.6 +TARG=bitbucket.org/ww/goautoneg
    12.7 +GOFILES=autoneg.go
    12.8 +
    12.9 +include $(GOROOT)/src/Make.pkg
   12.10 +
   12.11 +format:
   12.12 +	gofmt -w *.go
   12.13 +
   12.14 +docs:
   12.15 +	gomake clean
   12.16 +	godoc ${TARG} > README.txt
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/vendor/bitbucket.org/ww/goautoneg/README.txt	Mon Dec 14 00:12:33 2015 -0800
    13.3 @@ -0,0 +1,67 @@
    13.4 +PACKAGE
    13.5 +
    13.6 +package goautoneg
    13.7 +import "bitbucket.org/ww/goautoneg"
    13.8 +
    13.9 +HTTP Content-Type Autonegotiation.
   13.10 +
   13.11 +The functions in this package implement the behaviour specified in
   13.12 +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
   13.13 +
   13.14 +Copyright (c) 2011, Open Knowledge Foundation Ltd.
   13.15 +All rights reserved.
   13.16 +
   13.17 +Redistribution and use in source and binary forms, with or without
   13.18 +modification, are permitted provided that the following conditions are
   13.19 +met:
   13.20 +
   13.21 +    Redistributions of source code must retain the above copyright
   13.22 +    notice, this list of conditions and the following disclaimer.
   13.23 +
   13.24 +    Redistributions in binary form must reproduce the above copyright
   13.25 +    notice, this list of conditions and the following disclaimer in
   13.26 +    the documentation and/or other materials provided with the
   13.27 +    distribution.
   13.28 +
   13.29 +    Neither the name of the Open Knowledge Foundation Ltd. nor the
   13.30 +    names of its contributors may be used to endorse or promote
   13.31 +    products derived from this software without specific prior written
   13.32 +    permission.
   13.33 +
   13.34 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   13.35 +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   13.36 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   13.37 +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   13.38 +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   13.39 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   13.40 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   13.41 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   13.42 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   13.43 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   13.44 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   13.45 +
   13.46 +
   13.47 +FUNCTIONS
   13.48 +
   13.49 +func Negotiate(header string, alternatives []string) (content_type string)
   13.50 +Negotiate the most appropriate content_type given the accept header
   13.51 +and a list of alternatives.
   13.52 +
   13.53 +func ParseAccept(header string) (accept []Accept)
   13.54 +Parse an Accept Header string returning a sorted list
   13.55 +of clauses
   13.56 +
   13.57 +
   13.58 +TYPES
   13.59 +
   13.60 +type Accept struct {
   13.61 +    Type, SubType string
   13.62 +    Q             float32
   13.63 +    Params        map[string]string
   13.64 +}
   13.65 +Structure to represent a clause in an HTTP Accept Header
   13.66 +
   13.67 +
   13.68 +SUBDIRECTORIES
   13.69 +
   13.70 +	.hg
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/vendor/bitbucket.org/ww/goautoneg/autoneg.go	Mon Dec 14 00:12:33 2015 -0800
    14.3 @@ -0,0 +1,162 @@
    14.4 +/*
    14.5 +HTTP Content-Type Autonegotiation.
    14.6 +
    14.7 +The functions in this package implement the behaviour specified in
    14.8 +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
    14.9 +
   14.10 +Copyright (c) 2011, Open Knowledge Foundation Ltd.
   14.11 +All rights reserved.
   14.12 +
   14.13 +Redistribution and use in source and binary forms, with or without
   14.14 +modification, are permitted provided that the following conditions are
   14.15 +met:
   14.16 +
   14.17 +    Redistributions of source code must retain the above copyright
   14.18 +    notice, this list of conditions and the following disclaimer.
   14.19 +
   14.20 +    Redistributions in binary form must reproduce the above copyright
   14.21 +    notice, this list of conditions and the following disclaimer in
   14.22 +    the documentation and/or other materials provided with the
   14.23 +    distribution.
   14.24 +
   14.25 +    Neither the name of the Open Knowledge Foundation Ltd. nor the
   14.26 +    names of its contributors may be used to endorse or promote
   14.27 +    products derived from this software without specific prior written
   14.28 +    permission.
   14.29 +
   14.30 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   14.31 +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   14.32 +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   14.33 +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   14.34 +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   14.35 +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   14.36 +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   14.37 +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   14.38 +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   14.39 +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   14.40 +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   14.41 +
   14.42 +
   14.43 +*/
   14.44 +package goautoneg
   14.45 +
   14.46 +import (
   14.47 +	"sort"
   14.48 +	"strconv"
   14.49 +	"strings"
   14.50 +)
   14.51 +
   14.52 +// Structure to represent a clause in an HTTP Accept Header
   14.53 +type Accept struct {
   14.54 +	Type, SubType string
   14.55 +	Q             float64
   14.56 +	Params        map[string]string
   14.57 +}
   14.58 +
   14.59 +// For internal use, so that we can use the sort interface
   14.60 +type accept_slice []Accept
   14.61 +
   14.62 +func (accept accept_slice) Len() int {
   14.63 +	slice := []Accept(accept)
   14.64 +	return len(slice)
   14.65 +}
   14.66 +
   14.67 +func (accept accept_slice) Less(i, j int) bool {
   14.68 +	slice := []Accept(accept)
   14.69 +	ai, aj := slice[i], slice[j]
   14.70 +	if ai.Q > aj.Q {
   14.71 +		return true
   14.72 +	}
   14.73 +	if ai.Type != "*" && aj.Type == "*" {
   14.74 +		return true
   14.75 +	}
   14.76 +	if ai.SubType != "*" && aj.SubType == "*" {
   14.77 +		return true
   14.78 +	}
   14.79 +	return false
   14.80 +}
   14.81 +
   14.82 +func (accept accept_slice) Swap(i, j int) {
   14.83 +	slice := []Accept(accept)
   14.84 +	slice[i], slice[j] = slice[j], slice[i]
   14.85 +}
   14.86 +
   14.87 +// Parse an Accept Header string returning a sorted list
   14.88 +// of clauses
   14.89 +func ParseAccept(header string) (accept []Accept) {
   14.90 +	parts := strings.Split(header, ",")
   14.91 +	accept = make([]Accept, 0, len(parts))
   14.92 +	for _, part := range parts {
   14.93 +		part := strings.Trim(part, " ")
   14.94 +
   14.95 +		a := Accept{}
   14.96 +		a.Params = make(map[string]string)
   14.97 +		a.Q = 1.0
   14.98 +
   14.99 +		mrp := strings.Split(part, ";")
  14.100 +
  14.101 +		media_range := mrp[0]
  14.102 +		sp := strings.Split(media_range, "/")
  14.103 +		a.Type = strings.Trim(sp[0], " ")
  14.104 +
  14.105 +		switch {
  14.106 +		case len(sp) == 1 && a.Type == "*":
  14.107 +			a.SubType = "*"
  14.108 +		case len(sp) == 2:
  14.109 +			a.SubType = strings.Trim(sp[1], " ")
  14.110 +		default:
  14.111 +			continue
  14.112 +		}
  14.113 +
  14.114 +		if len(mrp) == 1 {
  14.115 +			accept = append(accept, a)
  14.116 +			continue
  14.117 +		}
  14.118 +
  14.119 +		for _, param := range mrp[1:] {
  14.120 +			sp := strings.SplitN(param, "=", 2)
  14.121 +			if len(sp) != 2 {
  14.122 +				continue
  14.123 +			}
  14.124 +			token := strings.Trim(sp[0], " ")
  14.125 +			if token == "q" {
  14.126 +				a.Q, _ = strconv.ParseFloat(sp[1], 32)
  14.127 +			} else {
  14.128 +				a.Params[token] = strings.Trim(sp[1], " ")
  14.129 +			}
  14.130 +		}
  14.131 +
  14.132 +		accept = append(accept, a)
  14.133 +	}
  14.134 +
  14.135 +	slice := accept_slice(accept)
  14.136 +	sort.Sort(slice)
  14.137 +
  14.138 +	return
  14.139 +}
  14.140 +
  14.141 +// Negotiate the most appropriate content_type given the accept header
  14.142 +// and a list of alternatives.
  14.143 +func Negotiate(header string, alternatives []string) (content_type string) {
  14.144 +	asp := make([][]string, 0, len(alternatives))
  14.145 +	for _, ctype := range alternatives {
  14.146 +		asp = append(asp, strings.SplitN(ctype, "/", 2))
  14.147 +	}
  14.148 +	for _, clause := range ParseAccept(header) {
  14.149 +		for i, ctsp := range asp {
  14.150 +			if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
  14.151 +				content_type = alternatives[i]
  14.152 +				return
  14.153 +			}
  14.154 +			if clause.Type == ctsp[0] && clause.SubType == "*" {
  14.155 +				content_type = alternatives[i]
  14.156 +				return
  14.157 +			}
  14.158 +			if clause.Type == "*" && clause.SubType == "*" {
  14.159 +				content_type = alternatives[i]
  14.160 +				return
  14.161 +			}
  14.162 +		}
  14.163 +	}
  14.164 +	return
  14.165 +}
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/vendor/code.secondbit.org/api.hg/api.go	Mon Dec 14 00:12:33 2015 -0800
    15.3 @@ -0,0 +1,137 @@
    15.4 +package api
    15.5 +
    15.6 +import (
    15.7 +	"encoding/json"
    15.8 +	"errors"
    15.9 +	"log"
   15.10 +	"net/http"
   15.11 +	"strings"
   15.12 +
   15.13 +	"bitbucket.org/ww/goautoneg"
   15.14 +
   15.15 +	"code.secondbit.org/uuid.hg"
   15.16 +
   15.17 +	"golang.org/x/net/context"
   15.18 +)
   15.19 +
   15.20 +const (
   15.21 +	RequestErrAccessDenied  = "access_denied"
   15.22 +	RequestErrInsufficient  = "insufficient"
   15.23 +	RequestErrOverflow      = "overflow"
   15.24 +	RequestErrInvalidValue  = "invalid_value"
   15.25 +	RequestErrInvalidFormat = "invalid_format"
   15.26 +	RequestErrMissing       = "missing"
   15.27 +	RequestErrNotFound      = "not_found"
   15.28 +	RequestErrConflict      = "conflict"
   15.29 +	RequestErrActOfGod      = "act_of_god"
   15.30 +)
   15.31 +
   15.32 +var (
   15.33 +	ActOfGodError      = []RequestError{{Slug: RequestErrActOfGod}}
   15.34 +	InvalidFormatError = []RequestError{{Slug: RequestErrInvalidFormat, Field: "/"}}
   15.35 +
   15.36 +	Encoders = []string{"application/json"}
   15.37 +
   15.38 +	ErrUserIDNotSet = errors.New("user ID not set")
   15.39 +)
   15.40 +
   15.41 +type RequestError struct {
   15.42 +	Slug   string `json:"error,omitempty"`
   15.43 +	Field  string `json:"field,omitempty"`
   15.44 +	Param  string `json:"param,omitempty"`
   15.45 +	Header string `json:"header,omitempty"`
   15.46 +}
   15.47 +
   15.48 +type ContextHandler func(context.Context, http.ResponseWriter, *http.Request)
   15.49 +
   15.50 +func NegotiateMiddleware(h http.Handler) http.Handler {
   15.51 +	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   15.52 +		if r.Header.Get("Accept") != "" {
   15.53 +			contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
   15.54 +			if contentType == "" {
   15.55 +				w.WriteHeader(http.StatusNotAcceptable)
   15.56 +				w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept")))
   15.57 +				return
   15.58 +			}
   15.59 +		}
   15.60 +		h.ServeHTTP(w, r)
   15.61 +	})
   15.62 +}
   15.63 +
   15.64 +func Encode(w http.ResponseWriter, r *http.Request, status int, resp interface{}) {
   15.65 +	contentType := goautoneg.Negotiate(r.Header.Get("Accept"), Encoders)
   15.66 +	w.Header().Set("content-type", contentType)
   15.67 +	w.WriteHeader(status)
   15.68 +	var err error
   15.69 +	switch contentType {
   15.70 +	case "application/json":
   15.71 +		enc := json.NewEncoder(w)
   15.72 +		err = enc.Encode(resp)
   15.73 +	default:
   15.74 +		enc := json.NewEncoder(w)
   15.75 +		err = enc.Encode(resp)
   15.76 +	}
   15.77 +	if err != nil {
   15.78 +		log.Println(err)
   15.79 +	}
   15.80 +}
   15.81 +
   15.82 +func Decode(r *http.Request, target interface{}) error {
   15.83 +	defer r.Body.Close()
   15.84 +	switch r.Header.Get("Content-Type") {
   15.85 +	case "application/json":
   15.86 +		dec := json.NewDecoder(r.Body)
   15.87 +		return dec.Decode(target)
   15.88 +	default:
   15.89 +		dec := json.NewDecoder(r.Body)
   15.90 +		return dec.Decode(target)
   15.91 +	}
   15.92 +}
   15.93 +
   15.94 +func CORSMiddleware(h http.Handler) http.Handler {
   15.95 +	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   15.96 +		w.Header().Set("Access-Control-Allow-Origin", "*")
   15.97 +		w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))
   15.98 +		w.Header().Set("Access-Control-Allow-Credentials", "true")
   15.99 +		if strings.ToLower(r.Method) == "options" {
  15.100 +			methods := strings.Join(r.Header[http.CanonicalHeaderKey("Trout-Methods")], ", ")
  15.101 +			w.Header().Set("Access-Control-Allow-Methods", methods)
  15.102 +			w.Header().Set("Allow", methods)
  15.103 +			w.WriteHeader(http.StatusOK)
  15.104 +			return
  15.105 +		}
  15.106 +		h.ServeHTTP(w, r)
  15.107 +	})
  15.108 +}
  15.109 +
  15.110 +func ContextWrapper(c context.Context, handler ContextHandler) http.Handler {
  15.111 +	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  15.112 +		handler(c, w, r)
  15.113 +	})
  15.114 +}
  15.115 +
  15.116 +func CheckScopes(r *http.Request, scopes ...string) bool {
  15.117 +	passedStr := r.Header.Get("scopes")
  15.118 +	passed := strings.Split(passedStr, " ")
  15.119 +	for _, scope := range scopes {
  15.120 +		var found bool
  15.121 +		for _, p := range passed {
  15.122 +			if scope == strings.TrimSpace(p) {
  15.123 +				found = true
  15.124 +				break
  15.125 +			}
  15.126 +		}
  15.127 +		if !found {
  15.128 +			return false
  15.129 +		}
  15.130 +	}
  15.131 +	return true
  15.132 +}
  15.133 +
  15.134 +func AuthUser(r *http.Request) (uuid.ID, error) {
  15.135 +	rawID := r.Header.Get("User-ID")
  15.136 +	if rawID == "" {
  15.137 +		return nil, ErrUserIDNotSet
  15.138 +	}
  15.139 +	return uuid.Parse(rawID)
  15.140 +}
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/vendor/code.secondbit.org/scopes.hg/types/scope.go	Mon Dec 14 00:12:33 2015 -0800
    16.3 @@ -0,0 +1,60 @@
    16.4 +package scopeTypes
    16.5 +
    16.6 +// Scope represents a limit on the access that a grant provides.
    16.7 +type Scope struct {
    16.8 +	ID          string
    16.9 +	Name        string
   16.10 +	Description string
   16.11 +}
   16.12 +
   16.13 +func ApplyChange(change ScopeChange, scope Scope) Scope {
   16.14 +	changed := scope
   16.15 +	if change.Name != nil {
   16.16 +		changed.Name = *change.Name
   16.17 +	}
   16.18 +	if change.Description != nil {
   16.19 +		changed.Description = *change.Description
   16.20 +	}
   16.21 +	return changed
   16.22 +}
   16.23 +
   16.24 +type Scopes []Scope
   16.25 +
   16.26 +func (s Scopes) Len() int {
   16.27 +	return len(s)
   16.28 +}
   16.29 +
   16.30 +func (s Scopes) Swap(i, j int) {
   16.31 +	s[i], s[j] = s[j], s[i]
   16.32 +}
   16.33 +
   16.34 +func (s Scopes) Less(i, j int) bool {
   16.35 +	return s[i].ID < s[j].ID
   16.36 +}
   16.37 +
   16.38 +func (s Scopes) Strings() []string {
   16.39 +	res := make([]string, len(s))
   16.40 +	for pos, scope := range s {
   16.41 +		res[pos] = scope.ID
   16.42 +	}
   16.43 +	return res
   16.44 +}
   16.45 +
   16.46 +// ScopeChange represents a change to a Scope.
   16.47 +type ScopeChange struct {
   16.48 +	ScopeID     string
   16.49 +	Name        *string
   16.50 +	Description *string
   16.51 +}
   16.52 +
   16.53 +func (s ScopeChange) Empty() bool {
   16.54 +	return s.Name == nil && s.Description == nil
   16.55 +}
   16.56 +
   16.57 +func StringsToScopes(s []string) Scopes {
   16.58 +	res := make(Scopes, len(s))
   16.59 +	for pos, scope := range s {
   16.60 +		res[pos] = Scope{ID: scope}
   16.61 +	}
   16.62 +	return res
   16.63 +}
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/vendor/code.secondbit.org/scopes.hg/types/scope_postgres.go	Mon Dec 14 00:12:33 2015 -0800
    17.3 @@ -0,0 +1,32 @@
    17.4 +package scopeTypes
    17.5 +
    17.6 +import (
    17.7 +	"database/sql/driver"
    17.8 +
    17.9 +	"code.secondbit.org/pqarrays.hg"
   17.10 +)
   17.11 +
   17.12 +func (s Scope) GetSQLTableName() string {
   17.13 +	return "scopes"
   17.14 +}
   17.15 +
   17.16 +func (s Scopes) Value() (driver.Value, error) {
   17.17 +	ids := make(pqarrays.StringArray, 0, len(s))
   17.18 +	for _, scope := range s {
   17.19 +		ids = append(ids, scope.ID)
   17.20 +	}
   17.21 +	return ids.Value()
   17.22 +}
   17.23 +
   17.24 +func (s *Scopes) Scan(value interface{}) error {
   17.25 +	*s = (*s)[:0]
   17.26 +	var ids pqarrays.StringArray
   17.27 +	err := ids.Scan(value)
   17.28 +	if err != nil {
   17.29 +		return err
   17.30 +	}
   17.31 +	for _, id := range ids {
   17.32 +		*s = append(*s, Scope{ID: id})
   17.33 +	}
   17.34 +	return nil
   17.35 +}
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/vendor/code.secondbit.org/trout.hg/LICENSE	Mon Dec 14 00:12:33 2015 -0800
    18.3 @@ -0,0 +1,10 @@
    18.4 +Copyright (c) 2015, Second Bit, LLC
    18.5 +
    18.6 +All rights reserved.
    18.7 +
    18.8 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    18.9 +
   18.10 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   18.11 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
   18.12 +Neither the name of the Second Bit, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
   18.13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/vendor/code.secondbit.org/trout.hg/doc.go	Mon Dec 14 00:12:33 2015 -0800
    19.3 @@ -0,0 +1,66 @@
    19.4 +/*
    19.5 +Package trout provides an opinionated router that's implemented
    19.6 +using a basic trie.
    19.7 +
    19.8 +The router is opinionated and biased towards basic RESTful
    19.9 +services. Its main constraint is that its URL templating is very
   19.10 +basic and has no support for regular expressions, prefix matching,
   19.11 +or anything other than a direct equality comparison, unlike many
   19.12 +routing libraries.
   19.13 +
   19.14 +The router is specifically designed to support users that want to
   19.15 +return correct information with HEAD requests, so it enables users
   19.16 +to retrieve a list of HTTP methods an Endpoint is configured to
   19.17 +respond to. It will not return the configurations an Endpoint is
   19.18 +implicitly configured to respond to by associated a Handler with the
   19.19 +Endpoint itself. These HTTP methods can be accessed through the
   19.20 +Trout-Methods header that is injected into the http.Request object.
   19.21 +Each method will be its own string in the slice.
   19.22 +
   19.23 +The router is also specifically designed to differentiate between a
   19.24 +404 (Not Found) response and a 405 (Method Not Allowed) response. It
   19.25 +will use the configured Handle404 http.Handler when no Endpoint is found
   19.26 +that matches the http.Request's Path property. It will use the
   19.27 +configured Handle405 http.Handler when an Endpoint is found for the
   19.28 +http.Request's Path, but the http.Request's Method has no Handler
   19.29 +associated with it. Setting a default http.Handler for the Endpoint will
   19.30 +result in the Handle405 http.Handler never being used for that Endpoint.
   19.31 +
   19.32 +To map an Endpoint to a http.Handler:
   19.33 +
   19.34 +	var router trout.Router
   19.35 +	router.Endpoint("/posts/{slug}/comments/{id}").Handler(postsHandler)
   19.36 +
   19.37 +All requests that match that URL structure will be passed to the postsHandler,
   19.38 +no matter what HTTP method they use.
   19.39 +
   19.40 +To map specific Methods to a http.Handler:
   19.41 +
   19.42 +	var router trout.Router
   19.43 +	router.Endpoint("/posts/{slug}").Methods("GET", "POST").Handler(postsHandler)
   19.44 +
   19.45 +Only requests that match that URL structure will be passed to the postsHandler,
   19.46 +and only if they use the GET or POST HTTP method.
   19.47 +
   19.48 +To access the URL parameter values inside a request, use the RequestVars helper:
   19.49 +
   19.50 +	func handler(w http.ResponseWriter, r *http.Request) {
   19.51 +		vars := trout.RequestVars(r)
   19.52 +		...
   19.53 +	}
   19.54 +
   19.55 +This will return an http.Header object containing the parameter values. They are
   19.56 +passed into the http.Handler by injecting them into the http.Request's Header property,
   19.57 +with the header key of "Trout-Params-{parameter}". The RequestVars helper is just a
   19.58 +convenience function to strip the prefix. Parameters are always passed without the curly
   19.59 +braces. Finally, if a parameter name is used multiple times in a single URL template, values
   19.60 +will be stored in the slice in the order they appeared in the template:
   19.61 +
   19.62 +	// for the template /posts/{id}/comments/{id}
   19.63 +	// filled with /posts/hello-world/comments/1
   19.64 +	vars := trout.RequestVars(r)
   19.65 +	fmt.Println(vars.Get("id")) // outputs `hello-world`
   19.66 +	fmt.Println(vars[http.CanonicalHeaderKey("id")]) // outputs `["hello-world", "1"]`
   19.67 +	fmt.Println(vars[http.CanonicalHeaderKey("id"})][1]) // outputs `1`
   19.68 +*/
   19.69 +package trout
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/vendor/code.secondbit.org/trout.hg/route.go	Mon Dec 14 00:12:33 2015 -0800
    20.3 @@ -0,0 +1,265 @@
    20.4 +package trout
    20.5 +
    20.6 +import (
    20.7 +	"net/http"
    20.8 +	"strconv"
    20.9 +	"strings"
   20.10 +	"time"
   20.11 +)
   20.12 +
   20.13 +var (
   20.14 +	default404Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   20.15 +		w.WriteHeader(http.StatusNotFound)
   20.16 +		w.Write([]byte("404 Not Found"))
   20.17 +		return
   20.18 +	}))
   20.19 +	default405Handler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   20.20 +		w.WriteHeader(http.StatusMethodNotAllowed)
   20.21 +		w.Write([]byte("405 Method Not Allowed"))
   20.22 +		return
   20.23 +	}))
   20.24 +)
   20.25 +
   20.26 +// RequestVars returns easy-to-access mappings of parameters to values for URL templates. Any {parameter} in
   20.27 +// your URL template will be available in the returned Header as a slice of strings, one for each instance of
   20.28 +// the {parameter}. In the case of a parameter name being used more than once in the same URL template, the
   20.29 +// values will be in the slice in the order they appeared in the template.
   20.30 +//
   20.31 +// Values can easily be accessed by using the .Get() method of the returned Header, though to access multiple
   20.32 +// values, they must be accessed through the map. All parameters use http.CanonicalHeaderKey for their formatting.
   20.33 +// When using .Get(), the parameter name will be transformed automatically. When utilising the Header as a map,
   20.34 +// the parameter name needs to have http.CanonicalHeaderKey applied manually.
   20.35 +func RequestVars(r *http.Request) http.Header {
   20.36 +	res := http.Header{}
   20.37 +	for h, v := range r.Header {
   20.38 +		stripped := strings.TrimPrefix(h, http.CanonicalHeaderKey("Trout-Param-"))
   20.39 +		if stripped != h {
   20.40 +			res[stripped] = v
   20.41 +		}
   20.42 +	}
   20.43 +	return res
   20.44 +}
   20.45 +
   20.46 +// Router defines a set of Endpoints that map requests to the http.Handlers. The http.Handler assigned to
   20.47 +// Handle404, if set, will be called when no Endpoint matches the current request. The http.Handler assigned
   20.48 +// to Handle405, if set, will be called when an Endpoint matches the current request, but has no http.Handler
   20.49 +// set for the HTTP method that the request used. Should either of these properties be unset, a default
   20.50 +// http.Handler will be used.
   20.51 +//
   20.52 +// The Router type is safe for use with empty values, but makes no attempt at concurrency-safety in adding
   20.53 +// Endpoints or in setting properties. It should also be noted that the adding Endpoints while simultaneously
   20.54 +// routing requests will lead to undefined and (almost certainly) undesirable behaviour. Routers are intended
   20.55 +// to be initialised with a set of Endpoints, and then start serving requests. Using them outside of this use
   20.56 +// case is unsupported.
   20.57 +type Router struct {
   20.58 +	t         *trie
   20.59 +	Handle404 http.Handler
   20.60 +	Handle405 http.Handler
   20.61 +}
   20.62 +
   20.63 +func (router *Router) serve404(w http.ResponseWriter, r *http.Request, t time.Time) {
   20.64 +	h := default404Handler
   20.65 +	if router.Handle404 != nil {
   20.66 +		h = router.Handle404
   20.67 +	}
   20.68 +	r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10))
   20.69 +	h.ServeHTTP(w, r)
   20.70 +}
   20.71 +
   20.72 +func (router *Router) serve405(w http.ResponseWriter, r *http.Request, t time.Time) {
   20.73 +	h := default405Handler
   20.74 +	if router.Handle405 != nil {
   20.75 +		h = router.Handle405
   20.76 +	}
   20.77 +	r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(t).Nanoseconds(), 10))
   20.78 +	h.ServeHTTP(w, r)
   20.79 +}
   20.80 +
   20.81 +func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   20.82 +	start := time.Now()
   20.83 +	if router.t == nil {
   20.84 +		router.serve404(w, r, start)
   20.85 +		return
   20.86 +	}
   20.87 +	pieces := strings.Split(strings.ToLower(strings.Trim(r.URL.Path, "/")), "/")
   20.88 +	router.t.RLock()
   20.89 +	defer router.t.RUnlock()
   20.90 +	branches := make([]*branch, len(pieces))
   20.91 +	path, ok := router.t.match(pieces)
   20.92 +	if !ok {
   20.93 +		router.serve404(w, r, start)
   20.94 +		return
   20.95 +	}
   20.96 +	b := router.t.branch
   20.97 +	for i, pos := range path {
   20.98 +		b = b.children[pos]
   20.99 +		branches[i] = b
  20.100 +	}
  20.101 +	v := vars(branches, pieces)
  20.102 +	for key, vals := range v {
  20.103 +		r.Header[http.CanonicalHeaderKey("Trout-Param-"+key)] = vals
  20.104 +	}
  20.105 +	ms := make([]string, len(b.methods))
  20.106 +	i := 0
  20.107 +	for m := range b.methods {
  20.108 +		ms[i] = m
  20.109 +		i = i + 1
  20.110 +	}
  20.111 +	r.Header[http.CanonicalHeaderKey("Trout-Methods")] = ms
  20.112 +	h := b.methods[r.Method]
  20.113 +	if h == nil {
  20.114 +		router.serve405(w, r, start)
  20.115 +		return
  20.116 +	}
  20.117 +	r.Header.Set("Trout-Timer", strconv.FormatInt(time.Now().Sub(start).Nanoseconds(), 10))
  20.118 +	h.ServeHTTP(w, r)
  20.119 +}
  20.120 +
  20.121 +// Endpoint defines a new Endpoint on the Router. The Endpoint should be a URL template, using curly braces
  20.122 +// to denote parameters that should be filled at runtime. For example, `{id}` denotes a parameter named `id`
  20.123 +// that should be filled with whatever the request has in that space.
  20.124 +//
  20.125 +// Parameters are always `/`-separated strings. There is no support for regular expressions or other limitations
  20.126 +// on what may be in those strings. A parameter is simply defined as "whatever is between these two / characters".
  20.127 +func (router *Router) Endpoint(e string) *Endpoint {
  20.128 +	e = strings.Trim(e, "/")
  20.129 +	e = strings.ToLower(e)
  20.130 +	pieces := strings.Split(e, "/")
  20.131 +	if router.t == nil {
  20.132 +		router.t = &trie{}
  20.133 +	}
  20.134 +	router.t.Lock()
  20.135 +	defer router.t.Unlock()
  20.136 +	if router.t.branch == nil {
  20.137 +		router.t.branch = &branch{
  20.138 +			parent:   nil,
  20.139 +			children: []*branch{},
  20.140 +			key:      "",
  20.141 +			isParam:  false,
  20.142 +			methods:  map[string]http.Handler{},
  20.143 +		}
  20.144 +	}
  20.145 +	closest := findClosestLeaf(pieces, router.t.branch)
  20.146 +	b := router.t.branch
  20.147 +	for _, pos := range closest {
  20.148 +		b = b.children[pos]
  20.149 +	}
  20.150 +	if len(closest) == len(pieces) {
  20.151 +		return (*Endpoint)(b)
  20.152 +	}
  20.153 +	offset := len(closest)
  20.154 +	for i := offset; i < len(pieces); i++ {
  20.155 +		piece := pieces[i]
  20.156 +		var isParam bool
  20.157 +		if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" {
  20.158 +			isParam = true
  20.159 +			piece = piece[1 : len(piece)-1]
  20.160 +		}
  20.161 +		b = b.addChild(piece, isParam)
  20.162 +	}
  20.163 +	return (*Endpoint)(b)
  20.164 +}
  20.165 +
  20.166 +func vars(path []*branch, pieces []string) map[string][]string {
  20.167 +	v := map[string][]string{}
  20.168 +	for pos, p := range path {
  20.169 +		if !p.isParam {
  20.170 +			continue
  20.171 +		}
  20.172 +		_, ok := v[p.key]
  20.173 +		if !ok {
  20.174 +			v[p.key] = []string{pieces[pos]}
  20.175 +			continue
  20.176 +		}
  20.177 +		v[p.key] = append(v[p.key], pieces[pos])
  20.178 +	}
  20.179 +	return v
  20.180 +}
  20.181 +
  20.182 +func findClosestLeaf(pieces []string, b *branch) []int {
  20.183 +	offset := 0
  20.184 +	path := []int{}
  20.185 +	longest := []int{}
  20.186 +	num := len(pieces)
  20.187 +	for i := 0; i < num; i++ {
  20.188 +		piece := pieces[i]
  20.189 +		var isParam bool
  20.190 +		if len(piece) > 0 && piece[0:1] == "{" && piece[len(piece)-1:] == "}" {
  20.191 +			isParam = true
  20.192 +			piece = piece[1 : len(piece)-1]
  20.193 +		}
  20.194 +		offset = pickNextRoute(b, offset, piece, isParam)
  20.195 +		if offset == -1 {
  20.196 +			if len(path) == 0 {
  20.197 +				// exhausted our options, bail
  20.198 +				break
  20.199 +			}
  20.200 +			// no match, maybe save this and backup
  20.201 +			if len(path) > len(longest) {
  20.202 +				longest = append([]int{}, path...) // copy them over so they don't get modified
  20.203 +			}
  20.204 +			path, offset = backup(path)
  20.205 +			offset = offset + 1
  20.206 +			b = b.parent
  20.207 +			i = i - 2
  20.208 +		} else {
  20.209 +			path = append(path, offset)
  20.210 +			b = b.children[offset]
  20.211 +			offset = 0
  20.212 +		}
  20.213 +	}
  20.214 +	if len(longest) < len(path) {
  20.215 +		longest = append([]int{}, path...)
  20.216 +	}
  20.217 +	return longest
  20.218 +}
  20.219 +
  20.220 +func pickNextRoute(b *branch, offset int, input string, variable bool) int {
  20.221 +	count := len(b.children)
  20.222 +	for i := offset; i < count; i++ {
  20.223 +		if b.children[i].key == input && b.isParam == variable {
  20.224 +			return i
  20.225 +		}
  20.226 +	}
  20.227 +	return -1
  20.228 +}
  20.229 +
  20.230 +// Endpoint defines a single URL template that requests can be matched against. It uses
  20.231 +// URL parameters to accept variables in the URL structure and make them available to
  20.232 +// the Handlers associated with the Endpoint.
  20.233 +type Endpoint branch
  20.234 +
  20.235 +// Handler associates the passed http.Handler with the Endpoint. This http.Handler will be
  20.236 +// used for all requests, regardless of the HTTP method they are using, unless overridden by
  20.237 +// the Methods method. Endpoints without a http.Handler associated with them will not be
  20.238 +// considered matches for requests, unless the request was made using an HTTP method that the
  20.239 +// Endpoint has an http.Handler mapped to.
  20.240 +func (e *Endpoint) Handler(h http.Handler) {
  20.241 +	(*branch)(e).setHandler("", h)
  20.242 +}
  20.243 +
  20.244 +// Methods simple returns a Methods object that will enable the mapping of the passed HTTP
  20.245 +// request methods to a Methods object. On its own, this function does not modify anything. It
  20.246 +// should, instead, be used as a friendly shorthand to get to the Methods.Handler method.
  20.247 +func (e *Endpoint) Methods(m ...string) Methods {
  20.248 +	return Methods{
  20.249 +		e: e,
  20.250 +		m: m,
  20.251 +	}
  20.252 +}
  20.253 +
  20.254 +// Methods defines a pairing of an Endpoint to the HTTP request methods that should be mapped to
  20.255 +// specific http.Handlers. Its sole purpose is to enable the Methods.Handler method.
  20.256 +type Methods struct {
  20.257 +	e *Endpoint
  20.258 +	m []string
  20.259 +}
  20.260 +
  20.261 +// Handler maps a Methods object to a specific http.Handler. This overrides the http.Handler
  20.262 +// associated with the Endpoint to only handle specific HTTP method(s).
  20.263 +func (m Methods) Handler(h http.Handler) {
  20.264 +	b := (*branch)(m.e)
  20.265 +	for _, method := range m.m {
  20.266 +		b.setHandler(method, h)
  20.267 +	}
  20.268 +}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/vendor/code.secondbit.org/trout.hg/trie.go	Mon Dec 14 00:12:33 2015 -0800
    21.3 @@ -0,0 +1,113 @@
    21.4 +package trout
    21.5 +
    21.6 +import (
    21.7 +	"net/http"
    21.8 +	"sort"
    21.9 +	"sync"
   21.10 +)
   21.11 +
   21.12 +type trie struct {
   21.13 +	branch *branch
   21.14 +	sync.RWMutex
   21.15 +}
   21.16 +
   21.17 +func (t *trie) match(input []string) ([]int, bool) {
   21.18 +	if t.branch == nil {
   21.19 +		t.branch = &branch{}
   21.20 +	}
   21.21 +	b := t.branch
   21.22 +	path := []int{}
   21.23 +	offset := 0
   21.24 +	num := len(input)
   21.25 +	for i := 0; i < num; i++ {
   21.26 +		// if we're on a nil branch, we're at the end of our line
   21.27 +		if b == nil {
   21.28 +			return path, false
   21.29 +		}
   21.30 +		offset = pickNextBranch(b, offset, input[i])
   21.31 +		if offset == -1 {
   21.32 +			if len(path) == 0 {
   21.33 +				// can't find it, bail
   21.34 +				return path, false
   21.35 +			}
   21.36 +			// no match, backup
   21.37 +			path, offset = backup(path)
   21.38 +			offset = offset + 1 // we want the next index from the previously matched one
   21.39 +			b = b.parent        // we need to be picking a branch from our parent, again
   21.40 +			i = i - 2           // back up to the choice before the one that got us here
   21.41 +		} else {
   21.42 +			path = append(path, offset)
   21.43 +			b = b.children[offset]
   21.44 +			offset = 0
   21.45 +		}
   21.46 +	}
   21.47 +	return path, true
   21.48 +}
   21.49 +
   21.50 +type branch struct {
   21.51 +	parent   *branch
   21.52 +	children []*branch
   21.53 +	key      string
   21.54 +	isParam  bool
   21.55 +	methods  map[string]http.Handler
   21.56 +}
   21.57 +
   21.58 +func (b *branch) Less(i, j int) bool {
   21.59 +	if b.children[i].key == b.children[j].key {
   21.60 +		return b.children[j].isParam && !b.children[i].isParam
   21.61 +	}
   21.62 +	return b.children[i].key < b.children[j].key
   21.63 +}
   21.64 +
   21.65 +func (b *branch) Len() int {
   21.66 +	return len(b.children)
   21.67 +}
   21.68 +
   21.69 +func (b *branch) Swap(i, j int) {
   21.70 +	b.children[i], b.children[j] = b.children[j], b.children[i]
   21.71 +}
   21.72 +
   21.73 +func (b *branch) addChild(key string, param bool) *branch {
   21.74 +	child := &branch{key: key, parent: b, isParam: param}
   21.75 +	if b.children == nil {
   21.76 +		b.children = []*branch{child}
   21.77 +		return child
   21.78 +	}
   21.79 +	b.children = append(b.children, child)
   21.80 +	sort.Sort(b)
   21.81 +	return child
   21.82 +}
   21.83 +
   21.84 +func (b *branch) check(input string) bool {
   21.85 +	if b.isParam && b.key != "" {
   21.86 +		return true
   21.87 +	}
   21.88 +	if b.key == input {
   21.89 +		return true
   21.90 +	}
   21.91 +	return false
   21.92 +}
   21.93 +
   21.94 +func (b *branch) setHandler(method string, handler http.Handler) {
   21.95 +	if b.methods == nil {
   21.96 +		b.methods = map[string]http.Handler{}
   21.97 +	}
   21.98 +	b.methods[method] = handler
   21.99 +}
  21.100 +
  21.101 +func pickNextBranch(b *branch, offset int, input string) int {
  21.102 +	count := len(b.children)
  21.103 +	for i := offset; i < count; i++ {
  21.104 +		if b.children[i].check(input) {
  21.105 +			return i
  21.106 +		}
  21.107 +	}
  21.108 +	return -1
  21.109 +}
  21.110 +
  21.111 +func backup(path []int) ([]int, int) {
  21.112 +	last := len(path) - 1
  21.113 +	pos := path[last]
  21.114 +	path = path[:last]
  21.115 +	return path, pos
  21.116 +}
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/vendor/code.secondbit.org/uuid.hg/LICENSE	Mon Dec 14 00:12:33 2015 -0800
    22.3 @@ -0,0 +1,10 @@
    22.4 +Copyright (c) 2015, Second Bit, LLC
    22.5 +
    22.6 +All rights reserved.
    22.7 +
    22.8 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    22.9 +
   22.10 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   22.11 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
   22.12 +Neither the name of the Second Bit, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
   22.13 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.